Fix urgent UI interaction issues

This commit is contained in:
Franz Rolfsvaag 2026-06-18 23:13:16 +02:00
parent 89326bb1f7
commit df94125089
9 changed files with 74 additions and 21 deletions

View File

@ -166,10 +166,6 @@ Current state on `experimental-okf` as of 2026-06-18: standalone OKF plugin work
- Add examples for fields that expect URLs, model names, provider names, paths, selectors, or structured values.
- Ensure labels and helper text are suitable for non-technical admins without removing important admin-level specificity.
- Review localization/translation keys if present so simplified wording remains consistent across languages.
- IMPORTANT: on mobile, the navbar extends the viewport vertically, meaning users can only see as far down as "log in with discord". The navbar needs to be the full height of the viewport, and ensure all contained elements are visible. Should contents overflow the viewport, integrate a very slim and minimalistic scrollbar.
- /plugins/moderation -> User notes should be deletable by admins
- Theme editor "Popout" button does not create the popout preview as intended.
- Users not logged in see the browser context menu instead of the lumi-custom context menu.
## Core Feedback System
@ -585,6 +581,7 @@ Current state on `experimental-feedback-system` as of 2026-06-18: the core feedb
## Done
- 2026-06-18: Fixed urgent UI tail items locally on `experimental-okf`: mobile sidebar now fits the viewport and scrolls internally, moderation notes can be permanently deleted by admins, both theme preview pop-out buttons open the preview, unauthenticated pages use the Lumi context menu with Feedback disabled, and the global savebar now submits URL-encoded settings data so normal settings routes actually save. No repo push yet.
- 2026-06-18: Updated OKF aliases/related questions to newline-only parsing and display so commas remain valid inside questions. No repo push yet.
- 2026-06-18: Continued `experimental-okf` locally: wired OKF into Lumi AI through the generic context provider hook, made AI context providers user/message-aware, and verified OKF AI context only includes published/approved entries and fields visible to the requester. No repo push yet.
- 2026-06-18: Continued `experimental-okf` locally: added shared core live user lookup, migrated OKF/Lumi AI/Moderation/Economy user-selection fields to it, fixed the moderation target search field, added OKF restore-from-version, and added OKF role-preview cards. No repo push yet.

View File

@ -302,6 +302,20 @@ module.exports = {
res.redirect(`/plugins/${PLUGIN_ID}`);
});
router.post("/notes/:id/delete", (req, res) => {
if (!req.session.user) {
return res.redirect("/");
}
if (!req.session.user?.isAdmin) {
return deny(res);
}
const deleted = deleteNote(db, req.params.id);
req.session.flash = deleted
? { type: "success", message: "Note deleted." }
: { type: "error", message: "Note not found." };
res.redirect(`/plugins/${PLUGIN_ID}`);
});
web.mount(`/plugins/${PLUGIN_ID}`, router, {
label: "Moderation",
role: "mod",
@ -636,6 +650,11 @@ function addNote(db, subjectId, note, createdById, createdByName) {
).run(crypto.randomUUID(), subjectId, note, createdById, createdByName, Date.now());
}
function deleteNote(db, id) {
const result = db.prepare("DELETE FROM moderation_notes WHERE id = ?").run(id);
return result.changes > 0;
}
function listActions(db, { limit = 200 } = {}) {
return db
.prepare(

View File

@ -290,6 +290,7 @@
<th>Note</th>
<th data-sort="by">By</th>
<th data-sort="date">Date</th>
<% if (isAdmin) { %><th>Actions</th><% } %>
</tr>
</thead>
<tbody>
@ -306,6 +307,13 @@
<td><%= note.note %></td>
<td><%= note.created_by_name || 'Staff' %></td>
<td><%= new Date(note.created_at).toLocaleString() %></td>
<% if (isAdmin) { %>
<td>
<form method="post" action="/plugins/moderation/notes/<%= note.id %>/delete" class="inline-form" data-confirm-mode="modal" data-confirm-text="Delete this moderation note? This removes the note permanently.">
<button type="submit" class="button danger">Delete</button>
</form>
</td>
<% } %>
</tr>
<% }) %>
</tbody>

View File

@ -1640,7 +1640,7 @@
setContextActionEnabled("paste", Boolean(pasteTarget && contextClipboardText.length > 0));
setContextActionEnabled("link", true);
setContextActionEnabled("reload", true);
setContextActionEnabled("feedback", true);
setContextActionEnabled("feedback", !contextMenu.querySelector('[data-context-action="feedback"][data-context-auth-required="true"]'));
};
const showContextMenu = async (event) => {

View File

@ -114,10 +114,17 @@
status.textContent = "Saving...";
try {
for (const form of forms) {
const body = new URLSearchParams();
for (const [key, value] of new FormData(form).entries()) {
if (typeof value === "string") body.append(key, value);
}
const response = await fetch(form.action || window.location.href, {
method: form.method || "POST",
body: new FormData(form),
headers: { Accept: "text/html,application/json" },
body,
headers: {
Accept: "text/html,application/json",
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
},
redirect: "follow"
});
if (!response.ok) throw new Error(`Save failed for ${form.action || "settings form"}.`);

View File

@ -234,7 +234,22 @@ body {
body.sidebar-collapsed .sidebar,
.sidebar {
width: min(19rem, 88vw);
height: 100dvh;
max-height: 100dvh;
overflow-y: auto;
overscroll-behavior: contain;
padding-top: var(--lumi-space-3);
scrollbar-width: thin;
scrollbar-color: var(--lumi-border) transparent;
}
.sidebar-nav {
min-height: 0;
overflow-y: visible;
}
.sidebar-footer {
flex-shrink: 0;
}
.sidebar-brand {

View File

@ -1930,7 +1930,11 @@ body .modal-backdrop.destructive-confirm-modal {
top: 0;
left: 0;
bottom: 0;
height: 100dvh;
max-height: 100dvh;
width: 260px;
overflow-y: auto;
overscroll-behavior: contain;
transform: translateX(-100%);
transition: transform 0.2s ease;
z-index: 20;

View File

@ -252,7 +252,8 @@
applyPreview();
});
editor.querySelector("[data-theme-popout]")?.addEventListener("click", () => {
editor.querySelectorAll("[data-theme-popout]").forEach((button) => {
button.addEventListener("click", () => {
popout = window.open("", "lumi-theme-preview", "width=430,height=760,menubar=no,toolbar=no,location=no,status=no");
const status = editor.querySelector("[data-theme-popout-status]");
if (!popout) {
@ -262,9 +263,11 @@
popout.document.open();
popout.document.write(previewShell());
popout.document.close();
popout.focus?.();
if (status) status.textContent = "Pop-out preview is open and updates with this editor.";
syncPopout();
});
});
applyPreview();
})();

View File

@ -125,6 +125,7 @@
</form>
</div>
</div>
<% } %>
<div class="lumi-context-menu" data-lumi-context-menu hidden role="menu" aria-label="Page actions">
<button type="button" role="menuitem" data-context-action="back" data-context-tooltip="Go back to the page you were viewing before this one.">Back</button>
<button type="button" role="menuitem" data-context-action="forward" data-context-tooltip="Return to the next page after going back.">Forward</button>
@ -133,9 +134,8 @@
<button type="button" role="menuitem" data-context-action="paste" data-context-tooltip="Insert the current clipboard text into the active field.">Paste</button>
<button type="button" role="menuitem" data-context-action="link" data-context-tooltip="Copy a link to this page, section, or nearby anchored element.">Link to here</button>
<button type="button" role="menuitem" data-context-action="reload" data-context-tooltip="Reload this page while asking the browser to refresh cached files.">Hard reload</button>
<button type="button" role="menuitem" data-context-action="feedback" data-context-tooltip="Create feedback for admins. Right-click a specific element to attach it as context.">Feedback</button>
<button type="button" role="menuitem" data-context-action="feedback" data-context-tooltip="<%= user ? 'Create feedback for admins. Right-click a specific element to attach it as context.' : 'Log in to submit feedback to admins.' %>" <%= user ? "" : "data-context-auth-required=\"true\"" %>>Feedback</button>
</div>
<% } %>
<div class="modal-backdrop destructive-confirm-modal" data-destructive-modal aria-hidden="true">
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="destructive-confirm-title">
<div class="modal-header">