make some overall QoL improvements
This commit is contained in:
+8
-6
@@ -221,12 +221,12 @@
|
||||
data-chat-action="personaState">Roleplay state</button>
|
||||
<button id="backupWorkspace"
|
||||
class="secondary-action chat-drawer-action"
|
||||
type="button">Backup workspace</button>
|
||||
type="button">Download workspace archive</button>
|
||||
<button id="importWorkspace"
|
||||
class="secondary-action chat-drawer-action"
|
||||
type="button">Import workspace</button>
|
||||
<input id="importWorkspaceFile" class="hidden" type="file"
|
||||
accept="application/json,.json" />
|
||||
accept="application/json,.json,.tar.gz,application/gzip" />
|
||||
</div>
|
||||
</details>
|
||||
<details class="chat-drawer-section chat-drawer-danger">
|
||||
@@ -316,7 +316,7 @@
|
||||
</div>
|
||||
|
||||
<div id="personaModal" class="modal-overlay side-panel-overlay hidden">
|
||||
<div class="modal-card persona-modal-card side-panel-card">
|
||||
<div class="modal-card persona-modal-card side-panel-card" role="dialog" aria-modal="true" aria-labelledby="personaFormTitle" tabindex="-1">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">Persona editor</p>
|
||||
@@ -380,7 +380,7 @@
|
||||
</div>
|
||||
|
||||
<div id="roleplayStarterModal" class="modal-overlay side-panel-overlay hidden">
|
||||
<div class="modal-card roleplay-starter-card side-panel-card">
|
||||
<div class="modal-card roleplay-starter-card side-panel-card" role="dialog" aria-modal="true" aria-labelledby="roleplayStarterTitle" tabindex="-1">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">Start roleplay</p>
|
||||
@@ -449,7 +449,7 @@
|
||||
</div>
|
||||
|
||||
<div id="onboardingModal" class="modal-overlay hidden">
|
||||
<div class="modal-card onboarding-card">
|
||||
<div class="modal-card onboarding-card" role="dialog" aria-modal="true" aria-labelledby="onboardingTitle" aria-describedby="onboardingSubtitle" tabindex="-1">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">Get started</p>
|
||||
@@ -466,7 +466,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" defer></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/15.0.12/marked.min.js"
|
||||
integrity="sha512-rCQgmUulW6f6QegOvTntKKb5IAoxTpGVCdWqYjkXEpzAns6XUFs8NKVqWe+KQpctp/EoRSFSuykVputqknLYMg=="
|
||||
crossorigin="anonymous" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js" defer></script>
|
||||
<script type="module" src="/js/script.js?v=20260930-chat-usability"></script>
|
||||
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
|
||||
|
||||
+17
-10
@@ -5,6 +5,7 @@ import { get, post, put } from "./api.js?v=20260909-account-controls";
|
||||
import { initializeAppearance } from "./themeController.js?v=20260909-account-controls";
|
||||
import { clearStatus, setStatus } from "./statusNotice.js?v=20260909-account-controls";
|
||||
import { populatePersonaForm, readPersonaForm } from "./personaForm.js?v=20260909-account-controls";
|
||||
import { openModal, closeModal, topModal } from "./modalFocus.js";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const marketPersonaList = $("marketPersonaList");
|
||||
@@ -31,7 +32,6 @@ const marketPreviewExampleDialogues = $("marketPreviewExampleDialogues");
|
||||
const marketPreviewIntro = $("marketPreviewIntro");
|
||||
const marketPreviewUserPersonaField = $("marketPreviewUserPersonaField");
|
||||
const marketPreviewUserPersonaSelect = $("marketPreviewUserPersonaSelect");
|
||||
const marketPreviewScenarioField = $("marketPreviewScenarioField");
|
||||
const marketPreviewScenario = $("marketPreviewScenario");
|
||||
const marketPreviewStepOne = $("marketPreviewStepOne");
|
||||
const marketPreviewStepTwo = $("marketPreviewStepTwo");
|
||||
@@ -74,7 +74,6 @@ const marketPersonaFields = {
|
||||
|
||||
const validMarketSorts = new Set(["best", "newest", "most_favorited", "most_popular", "top_rated", "alphabetical"]);
|
||||
let marketPersonasByType = { assistant: [], user: [] };
|
||||
let assistantPersonas = [];
|
||||
let userPersonas = [];
|
||||
let pendingMarketPersona = null;
|
||||
let editingPersonaId = null;
|
||||
@@ -90,7 +89,7 @@ function setMarketStatus(message, state = "") {
|
||||
}
|
||||
|
||||
function closeMarketPopup(value = null) {
|
||||
marketPopupModal.classList.add("hidden");
|
||||
closeModal(marketPopupModal);
|
||||
if (marketPopupResolver) {
|
||||
const resolve = marketPopupResolver;
|
||||
marketPopupResolver = null;
|
||||
@@ -115,7 +114,7 @@ function promptMarketPopup({
|
||||
marketPopupInput.placeholder = placeholder;
|
||||
marketPopupConfirm.textContent = confirmLabel;
|
||||
marketPopupField.classList.remove("hidden");
|
||||
marketPopupModal.classList.remove("hidden");
|
||||
openModal(marketPopupModal, marketPopupInput);
|
||||
marketPopupInput.focus();
|
||||
marketPopupInput.select();
|
||||
return new Promise((resolve) => {
|
||||
@@ -241,7 +240,7 @@ function setCollapsiblePreviewText(element, value, maxLength = 220) {
|
||||
}
|
||||
|
||||
function closeMarketPreview() {
|
||||
marketPreviewModal.classList.add("hidden");
|
||||
closeModal(marketPreviewModal);
|
||||
pendingMarketPersona = null;
|
||||
marketPreviewStep = 1;
|
||||
marketPreviewUserPersonaSelect.innerHTML = "";
|
||||
@@ -318,7 +317,7 @@ function openMarketPreview(persona) {
|
||||
marketPreviewConfirm.classList.remove("hidden");
|
||||
}
|
||||
renderMarketPreviewStep();
|
||||
marketPreviewModal.classList.remove("hidden");
|
||||
openModal(marketPreviewModal, isAssistant ? marketPreviewUserPersonaSelect : marketPreviewConfirm);
|
||||
}
|
||||
|
||||
function openPersonaForm(persona = null) {
|
||||
@@ -326,12 +325,11 @@ function openPersonaForm(persona = null) {
|
||||
marketPersonaFormTitle.textContent = persona ? "Edit AI Character" : "Create AI Character";
|
||||
populatePersonaForm(marketPersonaFields, persona || {});
|
||||
setPersonaFormNotice("");
|
||||
marketPersonaModal.classList.remove("hidden");
|
||||
marketPersonaName.focus();
|
||||
openModal(marketPersonaModal, marketPersonaName);
|
||||
}
|
||||
|
||||
function closePersonaForm() {
|
||||
marketPersonaModal.classList.add("hidden");
|
||||
closeModal(marketPersonaModal);
|
||||
editingPersonaId = null;
|
||||
setPersonaFormNotice("");
|
||||
}
|
||||
@@ -510,7 +508,6 @@ async function reportPersona(marketId) {
|
||||
async function loadOwnedPersonas() {
|
||||
const res = await get("/personas");
|
||||
if (res.error) return false;
|
||||
assistantPersonas = res.assistantPersonas || [];
|
||||
userPersonas = res.userPersonas || [];
|
||||
activeUserPersonaId = res.activeUserPersonaId || null;
|
||||
return true;
|
||||
@@ -608,9 +605,19 @@ marketPopupInput.addEventListener("keydown", (event) => {
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
closeMarketPopup(null);
|
||||
}
|
||||
});
|
||||
document.addEventListener("keydown", event => {
|
||||
if (event.key !== "Escape") return;
|
||||
const modal = topModal();
|
||||
if (!modal) return;
|
||||
event.preventDefault();
|
||||
if (modal === marketPopupModal) closeMarketPopup(null);
|
||||
else if (modal === marketPersonaModal) closePersonaForm();
|
||||
else if (modal === marketPreviewModal) closeMarketPreview();
|
||||
});
|
||||
marketSearchInput.addEventListener("input", applyMarketFilter);
|
||||
if (marketAssistantSort) {
|
||||
marketAssistantSort.addEventListener("change", () => setMarketSort("assistant", marketAssistantSort.value));
|
||||
|
||||
+56
-35
@@ -7,6 +7,7 @@ import { appendSources, createWebSearchToggle } from "./webSearch.js";
|
||||
import { appendAttachments, createAttachmentComposer } from "./attachments.js?v=20260930-chat-usability";
|
||||
import { createReasoningToggle, finishReasoning, updateReasoning } from "./reasoning.js?v=20260921-reasoning-capability";
|
||||
import { closeChatDrawer, openChatDrawer } from "./chatDrawer.js?v=20260909-account-controls";
|
||||
import { openModal, closeModal, topModal } from "./modalFocus.js";
|
||||
import { onboardingPrompts, requestedChatId } from "./constants.js?v=20260909-account-controls";
|
||||
import { getModelProfile } from "./modelProfiles.js?v=20260909-account-controls";
|
||||
import { applyTheme as applySharedTheme, applyThemeMode as applySharedThemeMode, readStoredAppearance } from "./themeController.js?v=20260909-account-controls";
|
||||
@@ -243,7 +244,7 @@ function markOnboardingCompleted() {
|
||||
|
||||
function closeOnboarding() {
|
||||
if (!onboardingModal) return;
|
||||
onboardingModal.classList.add("hidden");
|
||||
closeModal(onboardingModal);
|
||||
}
|
||||
|
||||
function renderOnboardingStep() {
|
||||
@@ -262,6 +263,7 @@ function renderOnboardingStep() {
|
||||
button.addEventListener("click", () => {
|
||||
onboardingIntent = key;
|
||||
renderOnboardingStep();
|
||||
onboardingChoices.querySelector(".active")?.focus();
|
||||
});
|
||||
onboardingChoices.appendChild(button);
|
||||
});
|
||||
@@ -299,7 +301,7 @@ function maybeShowOnboarding() {
|
||||
onboardingStep = 1;
|
||||
onboardingIntent = "ask";
|
||||
renderOnboardingStep();
|
||||
onboardingModal.classList.remove("hidden");
|
||||
openModal(onboardingModal, onboardingChoices.querySelector("button"));
|
||||
}
|
||||
function setMessageContent(el, content) {
|
||||
const markdownParser = window.marked;
|
||||
@@ -311,11 +313,6 @@ function setMessageContent(el, content) {
|
||||
}
|
||||
}
|
||||
function getChatById(id) { return chatSessions.find((chat) => chat.id === id); }
|
||||
function summarizeText(value, limit = 96) {
|
||||
const normalized = String(value || "").replace(/\s+/g, " ").trim();
|
||||
if (!normalized) return "";
|
||||
return normalized.length > limit ? `${normalized.slice(0, limit - 3).trimEnd()}...` : normalized;
|
||||
}
|
||||
function getPersonaName(id, items) {
|
||||
return items.find((persona) => persona.id === id)?.name || "";
|
||||
}
|
||||
@@ -434,10 +431,6 @@ function updateJumpToLatest() {
|
||||
messagesDiv.addEventListener("scroll", updateJumpToLatest, {passive: true});
|
||||
jumpToLatest.addEventListener("click", scrollMessagesToBottom);
|
||||
|
||||
function getNewestMessage() {
|
||||
return currentMessages.length ? currentMessages[currentMessages.length - 1] : null;
|
||||
}
|
||||
|
||||
function getMessageIndexById(messageId) {
|
||||
return currentMessages.findIndex((message) => message.id === messageId);
|
||||
}
|
||||
@@ -505,15 +498,6 @@ function syncRetryStateFromMessage(message) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function ensureRetryState(message) {
|
||||
if (!message?.id) return null;
|
||||
let state = messageRetryState.get(message.id);
|
||||
if (!state) {
|
||||
state = syncRetryStateFromMessage(message);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
async function openCreatedChat(chat) {
|
||||
if (!chat?.id) throw new Error("The server did not return the newly created chat");
|
||||
if (chatSearchInput) {
|
||||
@@ -587,7 +571,7 @@ async function editChatMessage(messageId, fallbackIndex = -1) {
|
||||
async function deleteChatMessage(messageId, fallbackIndex = -1) {
|
||||
const target = await resolveMessageTarget(messageId, fallbackIndex);
|
||||
if (!target) return setNotice("Message is not ready yet. Try again.", "error");
|
||||
const { index, message } = target;
|
||||
const { message } = target;
|
||||
const confirmed = await confirmPopup({
|
||||
eyebrow: message.role === "user" ? "Your message" : "Assistant message",
|
||||
title: "Delete message",
|
||||
@@ -1246,7 +1230,7 @@ function updateActivePersonaStatus(items, activeId, statusEl, emptyText, prefix)
|
||||
function openPersonaForm(persona = null, personaType = "assistant") {
|
||||
closeChatDrawer();
|
||||
closePopup(false);
|
||||
personaModal.classList.remove("hidden");
|
||||
openModal(personaModal, personaNameInput);
|
||||
if (persona) {
|
||||
editingPersonaId = persona.id; editingPersonaType = persona.persona_type || "assistant";
|
||||
personaFormTitle.textContent = `Edit ${editingPersonaType === "user" ? "your persona" : "AI Character"}`;
|
||||
@@ -1266,7 +1250,7 @@ function openPersonaForm(persona = null, personaType = "assistant") {
|
||||
personaNameInput.focus();
|
||||
}
|
||||
|
||||
function closePersonaForm() { personaModal.classList.add("hidden"); editingPersonaId = null; editingPersonaType = "assistant"; personaTypeSelect.disabled = false; setPersonaFormNotice(""); }
|
||||
function closePersonaForm() { closeModal(personaModal); editingPersonaId = null; editingPersonaType = "assistant"; personaTypeSelect.disabled = false; setPersonaFormNotice(""); }
|
||||
function populateRoleplayStarter() {
|
||||
if (!roleplayCharacterSelect || !roleplayUserPersonaSelect) return;
|
||||
roleplayCharacterSelect.innerHTML = "";
|
||||
@@ -1315,12 +1299,11 @@ function openRoleplayStarter(characterId = null) {
|
||||
populateRoleplayStarter();
|
||||
if (roleplayScenarioInput) roleplayScenarioInput.value = "";
|
||||
setRoleplayStarterNotice("");
|
||||
roleplayStarterModal.classList.remove("hidden");
|
||||
if (roleplayCharacterSelect.options.length) roleplayCharacterSelect.focus();
|
||||
openModal(roleplayStarterModal, roleplayCharacterSelect);
|
||||
}
|
||||
function closeRoleplayStarter() {
|
||||
if (!roleplayStarterModal) return;
|
||||
roleplayStarterModal.classList.add("hidden");
|
||||
closeModal(roleplayStarterModal);
|
||||
roleplayPresetCharacterId = null;
|
||||
roleplayStarterStep = 1;
|
||||
roleplayStarterConfirm.disabled = false;
|
||||
@@ -1349,7 +1332,7 @@ function finishPopup(result) {
|
||||
}
|
||||
function closePopup(resolveValue = null) {
|
||||
if (!popupModal || popupModal.classList.contains("hidden")) return;
|
||||
popupModal.classList.add("hidden");
|
||||
closeModal(popupModal);
|
||||
popupField.classList.add("hidden");
|
||||
popupInput.value = "";
|
||||
popupConfirmBtn.disabled = false;
|
||||
@@ -1377,7 +1360,7 @@ function openPopup(options) {
|
||||
} else {
|
||||
popupInput.value = "";
|
||||
}
|
||||
popupModal.classList.remove("hidden");
|
||||
openModal(popupModal, options.mode === "prompt" ? popupInput : popupConfirmBtn);
|
||||
return new Promise((resolve) => {
|
||||
popupResolver = resolve;
|
||||
requestAnimationFrame(() => {
|
||||
@@ -1801,18 +1784,18 @@ function exportCurrentChat() {
|
||||
}
|
||||
|
||||
async function backupWorkspace() {
|
||||
const response = await fetch("/exports/workspace");
|
||||
const data = await response.json();
|
||||
const blob = new Blob([JSON.stringify(data.workspace, null, 2)], { type: "application/json;charset=utf-8" });
|
||||
const response = await fetch("/exports/workspace/archive");
|
||||
if (!response.ok) return setNotice("Workspace archive could not be created.", "error");
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `workspace-backup-${new Date().toISOString().slice(0, 10)}.json`;
|
||||
link.download = `workspace-backup-${new Date().toISOString().slice(0, 10)}.tar.gz`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
setNotice("Workspace backup downloaded.", "success");
|
||||
setNotice("Workspace archive downloaded with images.", "success");
|
||||
}
|
||||
|
||||
function formatImportPreview(preview, warnings = []) {
|
||||
@@ -1829,6 +1812,31 @@ function formatImportPreview(preview, warnings = []) {
|
||||
async function importWorkspaceFromFile(file) {
|
||||
if (!file) return;
|
||||
setChatLoading(true, "Reading workspace backup...");
|
||||
const isArchive = file.name.toLowerCase().endsWith(".tar.gz");
|
||||
if (isArchive) {
|
||||
const submit = async (path, allowDuplicates = false) => {
|
||||
const body = new FormData();
|
||||
body.append("archive", file);
|
||||
if (allowDuplicates) body.append("allowDuplicates", "true");
|
||||
const response = await fetch(path, {method: "POST", body});
|
||||
return response.json();
|
||||
};
|
||||
try {
|
||||
const preview = await submit("/imports/workspace/archive/preview");
|
||||
setChatLoading(false);
|
||||
if (preview.error || preview.ok === false) return setNotice((preview.errors || []).slice(0, 4).join("; ") || preview.error || "Invalid archive.", "error");
|
||||
const confirmed = await confirmPopup({eyebrow: "Workspace import", title: "Review import",
|
||||
description: formatImportPreview(preview.preview || {}, preview.warnings || []), confirmLabel: "Import"});
|
||||
if (!confirmed) return;
|
||||
setChatLoading(true, "Importing workspace and images...");
|
||||
const result = await submit("/imports/workspace/archive", Boolean(preview.preview?.duplicates?.length));
|
||||
if (result.error) return setNotice(result.error, "error");
|
||||
await Promise.all([loadPersonas(), loadChatSessions()]);
|
||||
setNotice(`Workspace imported: ${result.imported.chats} chats with their images.`, "success");
|
||||
} catch { setNotice("Workspace archive could not be imported.", "error"); }
|
||||
finally { setChatLoading(false); }
|
||||
return;
|
||||
}
|
||||
let workspace;
|
||||
try {
|
||||
workspace = JSON.parse(await file.text());
|
||||
@@ -2020,7 +2028,9 @@ async function checkSession() {
|
||||
// Starters are offered on workspace home.
|
||||
const starter = new URLSearchParams(location.search).get("prompt");
|
||||
if (starter) { msgInput.value = starter; resizeComposerInput(); updateSendState(); }
|
||||
msgInput.focus(); return true;
|
||||
msgInput.focus();
|
||||
if (!starter && !activeChatId) maybeShowOnboarding();
|
||||
return true;
|
||||
}
|
||||
|
||||
async function sendMessage({ retryMessage = null } = {}) {
|
||||
@@ -2219,7 +2229,7 @@ if (headerModelButton) headerModelButton.addEventListener("click", (event) => {
|
||||
openModelSettings();
|
||||
});
|
||||
saveGenerationSettingsBtn.addEventListener("click", () => { void saveGenerationSettings(); });
|
||||
roleplayNewPersonaBtn.addEventListener("click", () => openPersonaForm(null, "assistant"));
|
||||
roleplayNewPersonaBtn.addEventListener("click", () => { closeRoleplayStarter(); openPersonaForm(null, "assistant"); });
|
||||
clearUserPersonaBtn.addEventListener("click", () => {
|
||||
closeChatDrawer();
|
||||
void clearPersona("user");
|
||||
@@ -2281,12 +2291,14 @@ if (onboardingContinue) {
|
||||
onboardingContinue.addEventListener("click", () => {
|
||||
onboardingStep = 2;
|
||||
renderOnboardingStep();
|
||||
onboardingChoices.querySelector("button")?.focus();
|
||||
});
|
||||
}
|
||||
if (onboardingBack) {
|
||||
onboardingBack.addEventListener("click", () => {
|
||||
onboardingStep = 1;
|
||||
renderOnboardingStep();
|
||||
(onboardingChoices.querySelector(".active") || onboardingChoices.querySelector("button"))?.focus();
|
||||
});
|
||||
}
|
||||
if (onboardingSkip) {
|
||||
@@ -2319,6 +2331,15 @@ document.addEventListener("keydown", (event) => {
|
||||
}
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
const activeModal = topModal();
|
||||
if (activeModal) {
|
||||
event.preventDefault();
|
||||
if (activeModal === popupModal) closePopup(false);
|
||||
else if (activeModal === personaModal) closePersonaForm();
|
||||
else if (activeModal === roleplayStarterModal) closeRoleplayStarter();
|
||||
else if (activeModal === onboardingModal) closeOnboarding();
|
||||
return;
|
||||
}
|
||||
const openMenu = document.querySelector(".msg-action-menu[open]");
|
||||
if (openMenu) {
|
||||
openMenu.open = false;
|
||||
|
||||
+14
-5
@@ -9,6 +9,7 @@ import {
|
||||
} from "./themeController.js?v=20260909-account-controls";
|
||||
import { clearStatus, setStatus } from "./statusNotice.js?v=20260909-account-controls";
|
||||
import { populatePersonaForm, readPersonaForm } from "./personaForm.js?v=20260909-account-controls";
|
||||
import { openModal, closeModal, topModal } from "./modalFocus.js";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const settingsNotice = $("settingsNotice");
|
||||
@@ -96,7 +97,7 @@ function setPersonaNotice(message = "", state = "") {
|
||||
}
|
||||
|
||||
function closeSettingsPopup(value = null) {
|
||||
settingsPopupModal.classList.add("hidden");
|
||||
closeModal(settingsPopupModal);
|
||||
if (settingsPopupResolver) {
|
||||
const resolve = settingsPopupResolver;
|
||||
settingsPopupResolver = null;
|
||||
@@ -120,7 +121,7 @@ function promptSettingsPopup({
|
||||
settingsPopupInput.value = value;
|
||||
settingsPopupInput.placeholder = placeholder;
|
||||
settingsPopupConfirm.textContent = confirmLabel;
|
||||
settingsPopupModal.classList.remove("hidden");
|
||||
openModal(settingsPopupModal, settingsPopupInput);
|
||||
settingsPopupInput.focus();
|
||||
settingsPopupInput.select();
|
||||
return new Promise((resolve) => {
|
||||
@@ -232,12 +233,11 @@ function openPersonaModal(persona = null, personaType = "assistant") {
|
||||
populatePersonaForm(settingsPersonaFields, persona || {});
|
||||
syncPersonaExamplesField();
|
||||
setPersonaNotice("");
|
||||
settingsPersonaModal.classList.remove("hidden");
|
||||
settingsPersonaName.focus();
|
||||
openModal(settingsPersonaModal, settingsPersonaName);
|
||||
}
|
||||
|
||||
function closePersonaModal() {
|
||||
settingsPersonaModal.classList.add("hidden");
|
||||
closeModal(settingsPersonaModal);
|
||||
editingPersonaId = null;
|
||||
setPersonaNotice("");
|
||||
}
|
||||
@@ -571,9 +571,18 @@ settingsPopupInput.addEventListener("keydown", (event) => {
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
closeSettingsPopup(null);
|
||||
}
|
||||
});
|
||||
document.addEventListener("keydown", event => {
|
||||
if (event.key !== "Escape") return;
|
||||
const modal = topModal();
|
||||
if (!modal) return;
|
||||
event.preventDefault();
|
||||
if (modal === settingsPopupModal) closeSettingsPopup(null);
|
||||
else if (modal === settingsPersonaModal) closePersonaModal();
|
||||
});
|
||||
|
||||
window.addEventListener("load", async () => {
|
||||
initializeAppearance({ title: "Elsewhere Settings", themeSelect: settingsThemeSelect, modeSelect: themeModeSelect });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file Verbindet die Abmeldeknöpfe des gemeinsamen Seitenkopfs mit der Sitzungsverwaltung.
|
||||
*/
|
||||
import {post} from './api.js?v=20260909-account-controls';
|
||||
import {post} from './api.js?v=20260909-account-controls';
|
||||
|
||||
// Every page gets this control from the same server-rendered header.
|
||||
for (const button of document.querySelectorAll('[data-sign-out]')) {
|
||||
|
||||
+2
-2
@@ -102,7 +102,7 @@
|
||||
</main>
|
||||
|
||||
<div id="marketPreviewModal" class="modal-overlay hidden">
|
||||
<div class="modal-card market-preview-card">
|
||||
<div class="modal-card market-preview-card" role="dialog" aria-modal="true" aria-labelledby="marketPreviewTitle" tabindex="-1">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<p class="eyebrow">Persona preview</p>
|
||||
@@ -162,7 +162,7 @@
|
||||
</div>
|
||||
|
||||
<div id="marketPersonaModal" class="modal-overlay hidden">
|
||||
<div class="modal-card persona-modal-card">
|
||||
<div class="modal-card persona-modal-card" role="dialog" aria-modal="true" aria-labelledby="marketPersonaFormTitle" tabindex="-1">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">AI Character editor</p>
|
||||
|
||||
@@ -232,7 +232,7 @@
|
||||
</main>
|
||||
|
||||
<div id="settingsPersonaModal" class="modal-overlay hidden">
|
||||
<div class="modal-card persona-modal-card">
|
||||
<div class="modal-card persona-modal-card" role="dialog" aria-modal="true" aria-labelledby="settingsPersonaFormTitle" tabindex="-1">
|
||||
<div class="modal-header">
|
||||
<div class="persona-modal-heading">
|
||||
<p class="eyebrow">Persona editor</p>
|
||||
|
||||
Reference in New Issue
Block a user