Multiple features. Admin page with verweeste items. Breadcrump working.
This commit is contained in:
@@ -1150,3 +1150,54 @@ body.editor-mode .editor-metadata-row {
|
||||
outline: 2px solid #e1c75a;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* 0.2.23: source-mode and section editing */
|
||||
.EasyMDEContainer.raw-markdown .CodeMirror .cm-header-1,
|
||||
.EasyMDEContainer.raw-markdown .CodeMirror .cm-header-2,
|
||||
.EasyMDEContainer.raw-markdown .CodeMirror .cm-header-3,
|
||||
.EasyMDEContainer.raw-markdown .CodeMirror .cm-header-4,
|
||||
.EasyMDEContainer.raw-markdown .CodeMirror .cm-header-5,
|
||||
.EasyMDEContainer.raw-markdown .CodeMirror .cm-header-6,
|
||||
.EasyMDEContainer.raw-markdown .CodeMirror .cm-strong,
|
||||
.EasyMDEContainer.raw-markdown .CodeMirror .cm-em {
|
||||
font-size: inherit !important;
|
||||
line-height: inherit !important;
|
||||
font-weight: normal !important;
|
||||
font-style: normal !important;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror .section-edit-highlight {
|
||||
background: #fff3a3;
|
||||
}
|
||||
|
||||
.wiki-auto-link {
|
||||
text-decoration-style: dotted;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.orphaned-uploads-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.orphaned-upload-row {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--wiki-border);
|
||||
}
|
||||
|
||||
.orphaned-upload-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.orphaned-upload-uses {
|
||||
margin: 4px 0 0 18px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.orphaned-upload-uses {
|
||||
margin: 4px 0 0 18px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -158,9 +158,18 @@
|
||||
<nav class="admin-menu">
|
||||
<a id="admin-users-link" href="#" data-tr="users">Users</a>
|
||||
<a id="admin-translations-link" href="#" data-tr="translations">Translations</a>
|
||||
<a id="admin-orphaned-uploads-link" href="#" data-tr="orphaned-uploads">Orphaned uploads</a>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section id="orphaned-uploads-view" class="hidden">
|
||||
<header class="page-header">
|
||||
<h1 data-tr="orphaned-uploads">Orphaned uploads</h1>
|
||||
<a id="close-orphaned-uploads" href="#" data-tr="back">Back</a>
|
||||
</header>
|
||||
<div id="orphaned-uploads-list" class="orphaned-uploads-list"></div>
|
||||
</section>
|
||||
|
||||
<section id="user-admin-view" class="hidden">
|
||||
<header class="page-header">
|
||||
<h1 data-tr="user-administration">User administration</h1>
|
||||
|
||||
+328
-12
@@ -12,7 +12,9 @@
|
||||
translationPage: "wiki-translations",
|
||||
translationTemplate: "",
|
||||
siteTitle: "Racket Wiki",
|
||||
bookmarks: []
|
||||
bookmarks: [],
|
||||
breadcrumbTrail: [],
|
||||
rawMarkdown: window.localStorage.getItem("racket-wiki-raw-markdown") === "true"
|
||||
};
|
||||
|
||||
let easyMDE = null;
|
||||
@@ -20,7 +22,7 @@
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
function show(viewId) {
|
||||
for (const id of ["page-view", "not-found-view", "editor-view", "search-view", "recent-view", "bookmarks-view", "todo-view", "graph-view", "history-view", "admin-view", "user-admin-view"]) {
|
||||
for (const id of ["page-view", "not-found-view", "editor-view", "search-view", "recent-view", "bookmarks-view", "todo-view", "graph-view", "history-view", "admin-view", "user-admin-view", "orphaned-uploads-view"]) {
|
||||
$(id).classList.toggle("hidden", id !== viewId);
|
||||
}
|
||||
$("page-action-links").classList.toggle("hidden", viewId !== "page-view");
|
||||
@@ -148,9 +150,112 @@
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeMentionText(text) {
|
||||
return String(text || "")
|
||||
.normalize("NFKD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.toLocaleLowerCase()
|
||||
.replace(/[^\p{L}\p{N}]+/gu, "");
|
||||
}
|
||||
|
||||
function camelCaseParts(text) {
|
||||
return String(text || "").match(/[A-Z][a-z0-9]+(?:[A-Z][A-Za-z0-9]*)+/g) || [];
|
||||
}
|
||||
|
||||
function mentionAliases(page) {
|
||||
const aliases = new Set();
|
||||
const values = [page.title, slugTitle(page.slug), page.slug.replaceAll("-", " ")];
|
||||
for (const value of values) {
|
||||
if (!value) continue;
|
||||
aliases.add(value);
|
||||
for (const camel of camelCaseParts(value)) aliases.add(camel);
|
||||
}
|
||||
return Array.from(aliases)
|
||||
.map((value) => ({ text: value, key: normalizeMentionText(value) }))
|
||||
.filter((alias) => alias.key.length >= 5);
|
||||
}
|
||||
|
||||
function pageMentionMap(currentSlug = null) {
|
||||
const map = new Map();
|
||||
for (const page of state.pages) {
|
||||
if (page.slug === currentSlug) continue;
|
||||
for (const alias of mentionAliases(page)) {
|
||||
if (!map.has(alias.key)) {
|
||||
map.set(alias.key, page);
|
||||
} else if (map.get(alias.key)?.slug !== page.slug) {
|
||||
map.set(alias.key, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function linkUnlinkedMentions(html, currentSlug = null) {
|
||||
const host = document.createElement("div");
|
||||
host.innerHTML = html || "";
|
||||
const aliases = pageMentionMap(currentSlug);
|
||||
if (aliases.size === 0) return host.innerHTML;
|
||||
|
||||
const walker = document.createTreeWalker(host, NodeFilter.SHOW_TEXT);
|
||||
const textNodes = [];
|
||||
while (walker.nextNode()) textNodes.push(walker.currentNode);
|
||||
|
||||
for (const textNode of textNodes) {
|
||||
const parent = textNode.parentElement;
|
||||
if (!parent || parent.closest("a, code, pre, script, style, textarea")) continue;
|
||||
const text = textNode.nodeValue || "";
|
||||
const words = Array.from(text.matchAll(/[\p{L}\p{N}]+/gu));
|
||||
if (words.length === 0) continue;
|
||||
|
||||
const replacements = [];
|
||||
let wordIndex = 0;
|
||||
while (wordIndex < words.length) {
|
||||
let match = null;
|
||||
const maxWords = Math.min(5, words.length - wordIndex);
|
||||
for (let count = maxWords; count >= 1; count -= 1) {
|
||||
const first = words[wordIndex];
|
||||
const last = words[wordIndex + count - 1];
|
||||
const start = first.index;
|
||||
const end = last.index + last[0].length;
|
||||
const candidate = text.slice(start, end);
|
||||
const page = aliases.get(normalizeMentionText(candidate));
|
||||
if (page) {
|
||||
match = { start, end, page };
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
replacements.push(match);
|
||||
while (wordIndex < words.length && words[wordIndex].index < match.end) wordIndex += 1;
|
||||
} else {
|
||||
wordIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (replacements.length === 0) continue;
|
||||
const fragment = document.createDocumentFragment();
|
||||
let position = 0;
|
||||
for (const replacement of replacements) {
|
||||
if (replacement.start > position) fragment.append(document.createTextNode(text.slice(position, replacement.start)));
|
||||
const link = document.createElement("a");
|
||||
link.href = `#/${encodeURIComponent(replacement.page.slug)}`;
|
||||
link.className = "wiki-auto-link";
|
||||
link.title = tr("automatic-wiki-link", "Automatic wiki link");
|
||||
link.textContent = text.slice(replacement.start, replacement.end);
|
||||
fragment.append(link);
|
||||
position = replacement.end;
|
||||
}
|
||||
if (position < text.length) fragment.append(document.createTextNode(text.slice(position)));
|
||||
textNode.replaceWith(fragment);
|
||||
}
|
||||
|
||||
return host.innerHTML;
|
||||
}
|
||||
|
||||
function renderMarkdown(markdown, pageSlug = null) {
|
||||
const html = easyMDE.markdown(expandTodoMarkup(markdown || "", pageSlug));
|
||||
return DOMPurify.sanitize(applyImageWidthMarkup(html));
|
||||
const withImages = applyImageWidthMarkup(html);
|
||||
return DOMPurify.sanitize(linkUnlinkedMentions(withImages, pageSlug));
|
||||
}
|
||||
|
||||
function slugTitle(slug) {
|
||||
@@ -277,6 +382,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
function applyRawMarkdownMode() {
|
||||
if (!easyMDE) return;
|
||||
editorContainer().classList.toggle("raw-markdown", state.rawMarkdown);
|
||||
}
|
||||
|
||||
function toggleRawMarkdown() {
|
||||
state.rawMarkdown = !state.rawMarkdown;
|
||||
window.localStorage.setItem("racket-wiki-raw-markdown", state.rawMarkdown ? "true" : "false");
|
||||
applyRawMarkdownMode();
|
||||
easyMDE.codemirror.refresh();
|
||||
}
|
||||
|
||||
function initializeEditor() {
|
||||
if (!window.EasyMDE) {
|
||||
throw new Error("EasyMDE is not installed. Open /setup to repair the frontend setup.");
|
||||
@@ -338,12 +455,15 @@
|
||||
"|",
|
||||
toolbarButton("undo", EasyMDE.undo, "undo", tr("undo", "Undo")),
|
||||
toolbarButton("redo", EasyMDE.redo, "repeat", tr("redo", "Redo")),
|
||||
toolbarButton("raw-markdown", toggleRawMarkdown, "file-text-o", tr("raw-markdown", "Raw Markdown"), { noDisable: true }),
|
||||
toolbarButton("preview", EasyMDE.togglePreview, "eye", tr("preview", "Preview"), { noDisable: true }),
|
||||
toolbarButton("side-by-side", EasyMDE.toggleSideBySide, "columns", tr("side-by-side", "Side by side"), { noDisable: true, noMobile: true }),
|
||||
toolbarButton("fullscreen", EasyMDE.toggleFullScreen, "arrows-alt", tr("fullscreen", "Fullscreen"), { noDisable: true, noMobile: true })
|
||||
]
|
||||
});
|
||||
|
||||
applyRawMarkdownMode();
|
||||
|
||||
easyMDE.codemirror.on("change", () => {
|
||||
if (!$("editor-view").classList.contains("hidden")) {
|
||||
renderEditorToc();
|
||||
@@ -513,10 +633,26 @@
|
||||
});
|
||||
}
|
||||
|
||||
let highlightedEditorLine = null;
|
||||
let highlightedEditorTimer = null;
|
||||
|
||||
function clearEditorLineHighlight() {
|
||||
if (!easyMDE || highlightedEditorLine === null) return;
|
||||
easyMDE.codemirror.removeLineClass(highlightedEditorLine, "background", "section-edit-highlight");
|
||||
highlightedEditorLine = null;
|
||||
if (highlightedEditorTimer) {
|
||||
window.clearTimeout(highlightedEditorTimer);
|
||||
highlightedEditorTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function focusEditorLine(line) {
|
||||
if (!easyMDE || line === null || line === undefined) return;
|
||||
clearEditorLineHighlight();
|
||||
easyMDE.codemirror.setCursor({ line, ch: 0 });
|
||||
easyMDE.codemirror.scrollIntoView({ line, ch: 0 }, 160);
|
||||
highlightedEditorLine = easyMDE.codemirror.addLineClass(line, "background", "section-edit-highlight");
|
||||
highlightedEditorTimer = window.setTimeout(clearEditorLineHighlight, 4500);
|
||||
easyMDE.codemirror.focus();
|
||||
}
|
||||
|
||||
@@ -527,6 +663,68 @@
|
||||
});
|
||||
}
|
||||
|
||||
const breadcrumbStorageKey = "racket-wiki-breadcrumb-trail";
|
||||
const breadcrumbTrailLimit = 8;
|
||||
|
||||
function saveBreadcrumbTrail() {
|
||||
window.sessionStorage.setItem(breadcrumbStorageKey, JSON.stringify(state.breadcrumbTrail));
|
||||
}
|
||||
|
||||
function loadBreadcrumbTrail() {
|
||||
let stored = [];
|
||||
try {
|
||||
stored = JSON.parse(window.sessionStorage.getItem(breadcrumbStorageKey) || "[]");
|
||||
} catch (_error) {
|
||||
stored = [];
|
||||
}
|
||||
|
||||
const pageSlugs = new Set(state.pages.map((page) => page.slug));
|
||||
state.breadcrumbTrail = Array.isArray(stored)
|
||||
? stored.filter((slug) => typeof slug === "string" && pageSlugs.has(slug))
|
||||
: [];
|
||||
saveBreadcrumbTrail();
|
||||
}
|
||||
|
||||
function clearBreadcrumbTrail() {
|
||||
state.breadcrumbTrail = [];
|
||||
saveBreadcrumbTrail();
|
||||
}
|
||||
|
||||
function recordPageVisit(page) {
|
||||
if (!page) return;
|
||||
|
||||
const firstPage = startPage();
|
||||
if (firstPage && page.slug === firstPage.slug) {
|
||||
clearBreadcrumbTrail();
|
||||
return;
|
||||
}
|
||||
|
||||
const existingIndex = state.breadcrumbTrail.indexOf(page.slug);
|
||||
if (existingIndex >= 0) {
|
||||
state.breadcrumbTrail = state.breadcrumbTrail.slice(0, existingIndex + 1);
|
||||
} else {
|
||||
state.breadcrumbTrail.push(page.slug);
|
||||
if (state.breadcrumbTrail.length > breadcrumbTrailLimit) {
|
||||
state.breadcrumbTrail = state.breadcrumbTrail.slice(-breadcrumbTrailLimit);
|
||||
}
|
||||
}
|
||||
saveBreadcrumbTrail();
|
||||
}
|
||||
|
||||
function truncateBreadcrumbTrail(slug) {
|
||||
const firstPage = startPage();
|
||||
if (firstPage && slug === firstPage.slug) {
|
||||
clearBreadcrumbTrail();
|
||||
return;
|
||||
}
|
||||
|
||||
const index = state.breadcrumbTrail.indexOf(slug);
|
||||
if (index >= 0) {
|
||||
state.breadcrumbTrail = state.breadcrumbTrail.slice(0, index + 1);
|
||||
saveBreadcrumbTrail();
|
||||
}
|
||||
}
|
||||
|
||||
function renderBreadcrumbs(items) {
|
||||
const breadcrumbs = $("breadcrumbs");
|
||||
breadcrumbs.replaceChildren();
|
||||
@@ -554,7 +752,8 @@
|
||||
if (item.href) {
|
||||
const link = document.createElement("a");
|
||||
link.href = item.href;
|
||||
if (item.href === "/") link.dataset.home = "true";
|
||||
if (item.home || item.href === "/") link.dataset.home = "true";
|
||||
if (item.slug) link.dataset.breadcrumbSlug = item.slug;
|
||||
link.textContent = item.label;
|
||||
breadcrumbs.append(link);
|
||||
} else {
|
||||
@@ -567,13 +766,36 @@
|
||||
}
|
||||
|
||||
function pageBreadcrumbs(page, suffix = null) {
|
||||
const items = [
|
||||
{ label: state.siteTitle, href: "/" },
|
||||
{ label: page?.title || page?.slug || "New page" }
|
||||
];
|
||||
const firstPage = startPage();
|
||||
const items = [];
|
||||
|
||||
if (firstPage) {
|
||||
items.push({
|
||||
label: state.siteTitle,
|
||||
href: `#/${encodeURIComponent(firstPage.slug)}`,
|
||||
slug: firstPage.slug,
|
||||
home: true
|
||||
});
|
||||
} else {
|
||||
items.push({ label: state.siteTitle });
|
||||
}
|
||||
|
||||
for (const slug of state.breadcrumbTrail) {
|
||||
const trailPage = state.pages.find((item) => item.slug === slug);
|
||||
if (!trailPage) continue;
|
||||
const isCurrent = page && trailPage.slug === page.slug && !suffix;
|
||||
items.push({
|
||||
label: trailPage.title,
|
||||
href: isCurrent ? null : `#/${encodeURIComponent(trailPage.slug)}`,
|
||||
slug: trailPage.slug
|
||||
});
|
||||
}
|
||||
|
||||
if (page && firstPage && page.slug === firstPage.slug && suffix) {
|
||||
items[0].href = `#/${encodeURIComponent(firstPage.slug)}`;
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
items[items.length - 1].href = `#/${encodeURIComponent(page.slug)}`;
|
||||
items.push({ label: suffix });
|
||||
}
|
||||
|
||||
@@ -601,6 +823,7 @@
|
||||
|
||||
async function goHome() {
|
||||
const firstPage = startPage();
|
||||
clearBreadcrumbTrail();
|
||||
if (!firstPage) {
|
||||
await route();
|
||||
return;
|
||||
@@ -708,6 +931,7 @@
|
||||
async function openPage(slug) {
|
||||
const page = await api(`/api/pages/${encodeURIComponent(slug)}`);
|
||||
state.currentPage = page;
|
||||
recordPageVisit(page);
|
||||
state.editingNew = false;
|
||||
state.newPageSlug = null;
|
||||
$("page-title").textContent = page.title;
|
||||
@@ -1109,6 +1333,82 @@
|
||||
show("admin-view");
|
||||
}
|
||||
|
||||
function formatFileSize(size) {
|
||||
const bytes = Number(size || 0);
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
async function loadOrphanedUploadsAdmin() {
|
||||
renderBreadcrumbs([
|
||||
{ label: state.siteTitle, href: "/" },
|
||||
{ label: tr("admin", "Admin") },
|
||||
{ label: tr("orphaned-uploads", "Orphaned uploads") }
|
||||
]);
|
||||
show("orphaned-uploads-view");
|
||||
const result = await api("/api/admin/uploads/orphaned");
|
||||
const list = $("orphaned-uploads-list");
|
||||
list.replaceChildren();
|
||||
|
||||
if (!result.uploads || result.uploads.length === 0) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "muted";
|
||||
empty.textContent = tr("no-orphaned-uploads", "No orphaned uploads.");
|
||||
list.append(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const upload of result.uploads) {
|
||||
const row = document.createElement("article");
|
||||
row.className = "orphaned-upload-row";
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = upload.originalName;
|
||||
const meta = document.createElement("div");
|
||||
meta.className = "muted";
|
||||
meta.textContent = `${upload.mimeType} · ${formatFileSize(upload.size)} · ${tr("uploaded-by", "uploaded by")} ${upload.uploadedBy}`;
|
||||
const history = document.createElement("div");
|
||||
const lastUses = Array.isArray(upload.lastUses) ? upload.lastUses : [];
|
||||
if (lastUses.length > 0) {
|
||||
const label = document.createElement("div");
|
||||
label.textContent = `${tr("last-used", "Last used")}:`;
|
||||
history.append(label);
|
||||
const uses = document.createElement("ul");
|
||||
uses.className = "orphaned-upload-uses";
|
||||
for (const use of lastUses) {
|
||||
const item = document.createElement("li");
|
||||
const link = document.createElement("a");
|
||||
link.href = `#/${encodeURIComponent(use.slug)}`;
|
||||
link.textContent = use.title || use.slug;
|
||||
item.append(link);
|
||||
if (use.version) item.append(document.createTextNode(` · ${tr("version", "Version")} ${use.version}`));
|
||||
uses.append(item);
|
||||
}
|
||||
history.append(uses);
|
||||
} else {
|
||||
history.textContent = tr("never-referenced", "Never referenced by a saved page.");
|
||||
}
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "orphaned-upload-actions";
|
||||
const view = document.createElement("a");
|
||||
view.href = `/uploads/${encodeURIComponent(upload.ownerSlug)}/${encodeURIComponent(upload.storedName)}`;
|
||||
view.target = "_blank";
|
||||
view.rel = "noopener";
|
||||
view.textContent = tr("view", "View");
|
||||
const remove = document.createElement("button");
|
||||
remove.className = "danger";
|
||||
remove.textContent = tr("delete", "Delete");
|
||||
remove.addEventListener("click", async () => {
|
||||
if (!confirm(tr("delete-orphaned-upload-confirm", "Delete this orphaned upload?"))) return;
|
||||
await api(`/api/admin/uploads/orphaned/${upload.id}`, { method: "DELETE" });
|
||||
await loadOrphanedUploadsAdmin();
|
||||
});
|
||||
actions.append(view, remove);
|
||||
row.append(title, meta, history, actions);
|
||||
list.append(row);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUsersAdmin() {
|
||||
renderBreadcrumbs([
|
||||
{ label: state.siteTitle, href: "/" },
|
||||
@@ -1608,6 +1908,7 @@
|
||||
$("account-role").textContent = tr(`role-${state.session.user.role}`, state.session.user.role);
|
||||
updateRoleUi();
|
||||
await loadPages();
|
||||
loadBreadcrumbTrail();
|
||||
await loadBookmarks();
|
||||
await route();
|
||||
startKeepAlive();
|
||||
@@ -1634,10 +1935,23 @@
|
||||
goHome().catch((error) => console.error(error));
|
||||
});
|
||||
$("breadcrumbs").addEventListener("click", (event) => {
|
||||
const home = event.target.closest("a[data-home='true']");
|
||||
if (!home) return;
|
||||
const link = event.target.closest("a[data-home='true'], a[data-breadcrumb-slug]");
|
||||
if (!link) return;
|
||||
|
||||
event.preventDefault();
|
||||
goHome().catch((error) => console.error(error));
|
||||
const slug = link.dataset.breadcrumbSlug;
|
||||
if (link.dataset.home === "true") {
|
||||
goHome().catch((error) => console.error(error));
|
||||
return;
|
||||
}
|
||||
|
||||
truncateBreadcrumbTrail(slug);
|
||||
const targetHash = `#/${encodeURIComponent(slug)}`;
|
||||
if (location.hash === targetHash) {
|
||||
openPage(slug).catch((error) => console.error(error));
|
||||
} else {
|
||||
location.hash = targetHash;
|
||||
}
|
||||
});
|
||||
$("recent-link").addEventListener("click", (event) => { event.preventDefault(); showRecent().catch((error) => console.error(error)); });
|
||||
$("bookmarks-link").addEventListener("click", (event) => { event.preventDefault(); showBookmarks().catch((error) => console.error(error)); });
|
||||
@@ -1651,6 +1965,7 @@
|
||||
});
|
||||
$("admin-link").addEventListener("click", (event) => { event.preventDefault(); showAdmin(); });
|
||||
$("admin-users-link").addEventListener("click", (event) => { event.preventDefault(); loadUsersAdmin().catch(console.error); });
|
||||
$("admin-orphaned-uploads-link").addEventListener("click", (event) => { event.preventDefault(); loadOrphanedUploadsAdmin().catch(console.error); });
|
||||
$("admin-translations-link").addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
const page = state.pages.find((item) => item.slug === state.translationPage);
|
||||
@@ -1665,6 +1980,7 @@
|
||||
beginNewPage(state.translationPage);
|
||||
});
|
||||
$("close-history").addEventListener("click", () => show("page-view"));
|
||||
$("close-orphaned-uploads").addEventListener("click", (event) => { event.preventDefault(); showAdmin(); });
|
||||
$("close-user-admin").addEventListener("click", (event) => { event.preventDefault(); showAdmin(); });
|
||||
$("cancel-edit").addEventListener("click", () => {
|
||||
if (state.currentPage) {
|
||||
|
||||
Reference in New Issue
Block a user