small changes
This commit is contained in:
@@ -122,6 +122,10 @@ button { cursor: pointer; }
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .editor-toolbar button.rw-mde-save {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror,
|
||||
.EasyMDEContainer .CodeMirror-scroll,
|
||||
.EasyMDEContainer .editor-preview-side {
|
||||
@@ -222,6 +226,62 @@ body {
|
||||
padding: 18px 16px 28px;
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
display: grid;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin: -8px -8px 8px auto;
|
||||
place-items: center;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
background: transparent;
|
||||
color: var(--wiki-link);
|
||||
}
|
||||
|
||||
.sidebar-toggle:hover,
|
||||
.sidebar-toggle:focus-visible {
|
||||
background: #fff;
|
||||
border-color: var(--wiki-border);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.sidebar-toggle svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
stroke-width: 1.8;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #app {
|
||||
grid-template-columns: 52px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #sidebar {
|
||||
padding: 12px 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #sidebar > :not(#sidebar-toggle):not(.sidebar-icon-nav) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #sidebar .sidebar-toggle {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #sidebar .sidebar-icon-nav {
|
||||
flex-direction: column;
|
||||
margin: 14px 0 0;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #sidebar .sidebar-icon-link {
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #sidebar .sidebar-icon-link + .sidebar-icon-link {
|
||||
border-top: 1px solid #c8cbd0;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: block;
|
||||
color: #111;
|
||||
@@ -909,6 +969,10 @@ body.editor-mode .EasyMDEContainer .editor-toolbar.fullscreen {
|
||||
background: white;
|
||||
}
|
||||
|
||||
body.editor-mode .EasyMDEContainer .editor-toolbar.fullscreen button.rw-mde-save {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
body.editor-mode .EasyMDEContainer .editor-statusbar.fullscreen {
|
||||
bottom: 0;
|
||||
min-height: 28px;
|
||||
@@ -972,6 +1036,14 @@ body.editor-mode .EasyMDEContainer .editor-preview-side {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
body.editor-mode .editor-shell .EasyMDEContainer:not(:has(.editor-preview-active-side)) {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
body.editor-mode .editor-shell .EasyMDEContainer:not(:has(.editor-preview-active-side)) .CodeMirror {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
body.editor-mode .EasyMDEContainer .CodeMirror-sided {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
<div id="connection-status" class="connection-status hidden" role="status"></div>
|
||||
<div id="app">
|
||||
<aside id="sidebar">
|
||||
<button id="sidebar-toggle" class="sidebar-toggle" type="button" title="Collapse sidebar" data-tr-title="collapse-sidebar" aria-label="Collapse sidebar" data-tr-aria-label="collapse-sidebar" aria-controls="sidebar" aria-expanded="true">
|
||||
<i data-lucide="panel-left-close" aria-hidden="true"></i>
|
||||
</button>
|
||||
<a id="wiki-brand" class="brand" href="#">Racket Wiki</a>
|
||||
<nav class="sidebar-icon-nav" aria-label="Wiki navigation">
|
||||
<a id="bookmarks-link" class="sidebar-icon-link" href="#bookmarks" title="Bookmarks" data-tr-title="bookmarks" aria-label="Bookmarks" data-tr-aria-label="bookmarks"><i data-lucide="star"></i><span class="visually-hidden" data-tr="bookmarks">Bookmarks</span></a>
|
||||
|
||||
+102
-8
@@ -13,6 +13,7 @@ import {
|
||||
expandNamespacedMarkdownLinks,
|
||||
expandTodoMarkup,
|
||||
expandWikiMentions,
|
||||
normalizeMarkdownLinkDestinations,
|
||||
protectCamelCaseWikiWords,
|
||||
extractCmapEmbeds,
|
||||
restoreCmapEmbeds
|
||||
@@ -70,6 +71,8 @@ import { ComboBox } from "./widgets/combobox.js";
|
||||
|
||||
let easyMDE = null;
|
||||
let pendingWikiCmapLinkLabel = "";
|
||||
const sidebarPreferenceKey = "racket-wiki-sidebar-collapsed";
|
||||
const editorSideBySidePreferenceKey = "racket-wiki-editor-side-by-side";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// General UI and HTTP support
|
||||
@@ -133,6 +136,50 @@ import { ComboBox } from "./widgets/combobox.js";
|
||||
document.documentElement.lang = state.language || "en";
|
||||
}
|
||||
|
||||
function setSidebarCollapsed(collapsed, remember = true) {
|
||||
document.body.classList.toggle("sidebar-collapsed", collapsed);
|
||||
const toggle = $("sidebar-toggle");
|
||||
if (!toggle) return;
|
||||
const translationKey = collapsed ? "expand-sidebar" : "collapse-sidebar";
|
||||
toggle.dataset.trTitle = translationKey;
|
||||
toggle.dataset.trAriaLabel = translationKey;
|
||||
toggle.title = tr(translationKey, collapsed ? "Expand sidebar" : "Collapse sidebar");
|
||||
toggle.setAttribute("aria-label", toggle.title);
|
||||
toggle.setAttribute("aria-expanded", String(!collapsed));
|
||||
const icon = document.createElement("i");
|
||||
icon.dataset.lucide = collapsed ? "panel-left-open" : "panel-left-close";
|
||||
icon.setAttribute("aria-hidden", "true");
|
||||
const oldIcon = toggle.querySelector("[data-lucide], svg");
|
||||
if (oldIcon) {
|
||||
oldIcon.replaceWith(icon);
|
||||
} else {
|
||||
toggle.append(icon);
|
||||
}
|
||||
if (window.lucide && typeof window.lucide.createIcons === "function") {
|
||||
window.lucide.createIcons();
|
||||
}
|
||||
if (remember) {
|
||||
try {
|
||||
window.sessionStorage.setItem(sidebarPreferenceKey, collapsed ? "true" : "false");
|
||||
} catch (_error) {
|
||||
// A blocked session storage should not prevent the toggle from working.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeSidebarToggle() {
|
||||
const toggle = $("sidebar-toggle");
|
||||
if (!toggle) return;
|
||||
let collapsed = false;
|
||||
try {
|
||||
collapsed = window.sessionStorage.getItem(sidebarPreferenceKey) === "true";
|
||||
} catch (_error) {
|
||||
collapsed = false;
|
||||
}
|
||||
setSidebarCollapsed(collapsed, false);
|
||||
toggle.addEventListener("click", () => setSidebarCollapsed(!document.body.classList.contains("sidebar-collapsed")));
|
||||
}
|
||||
|
||||
function navigateToHash(targetHash) {
|
||||
const navigate = () => {
|
||||
if (location.hash === targetHash) return route();
|
||||
@@ -204,7 +251,8 @@ import { ComboBox } from "./widgets/combobox.js";
|
||||
|
||||
function renderMarkdown(markdown, pageSlug = null) {
|
||||
const extracted = extractCmapEmbeds(markdown || "");
|
||||
const withExplicitWikiLinks = expandNamespacedMarkdownLinks(extracted.markdown);
|
||||
const normalizedLinks = normalizeMarkdownLinkDestinations(extracted.markdown);
|
||||
const withExplicitWikiLinks = expandNamespacedMarkdownLinks(normalizedLinks);
|
||||
const withWikiLinks = expandWikiMentions(
|
||||
withExplicitWikiLinks,
|
||||
state.pages,
|
||||
@@ -436,6 +484,32 @@ import { ComboBox } from "./widgets/combobox.js";
|
||||
easyMDE.codemirror.refresh();
|
||||
}
|
||||
|
||||
function preferredEditorSideBySide() {
|
||||
try {
|
||||
const stored = window.sessionStorage.getItem(editorSideBySidePreferenceKey);
|
||||
return stored === null ? true : stored === "true";
|
||||
} catch (_error) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function rememberEditorSideBySide() {
|
||||
if (!easyMDE) return;
|
||||
try {
|
||||
window.sessionStorage.setItem(
|
||||
editorSideBySidePreferenceKey,
|
||||
String(easyMDE.isSideBySideActive())
|
||||
);
|
||||
} catch (_error) {
|
||||
// A blocked session storage should not prevent the editor from working.
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEditorSideBySide() {
|
||||
easyMDE.toggleSideBySide();
|
||||
rememberEditorSideBySide();
|
||||
}
|
||||
|
||||
function protectCamelCaseInPage() {
|
||||
if (!easyMDE) return;
|
||||
const source = easyMDE.value();
|
||||
@@ -523,9 +597,10 @@ import { ComboBox } from "./widgets/combobox.js";
|
||||
"|",
|
||||
toolbarButton("undo", EasyMDE.undo, "undo-2", tr("undo", "Undo")),
|
||||
toolbarButton("redo", EasyMDE.redo, "redo-2", tr("redo", "Redo")),
|
||||
toolbarButton("save", savePageFromFullscreen, "save", tr("save", "Save"), { noDisable: true }),
|
||||
toolbarButton("raw-markdown", toggleRawMarkdown, "file-text", tr("raw-markdown", "Raw Markdown"), { noDisable: true }),
|
||||
toolbarButton("preview", EasyMDE.togglePreview, "eye", tr("preview", "Preview"), { noDisable: true }),
|
||||
toolbarButton("side-by-side", EasyMDE.toggleSideBySide, "columns-2", tr("side-by-side", "Side by side"), { noDisable: true, noMobile: true }),
|
||||
toolbarButton("side-by-side", toggleEditorSideBySide, "columns-2", tr("side-by-side", "Side by side"), { noDisable: true, noMobile: true }),
|
||||
toolbarButton("fullscreen", EasyMDE.toggleFullScreen, "maximize", tr("fullscreen", "Fullscreen"), { noDisable: true, noMobile: true })
|
||||
]
|
||||
});
|
||||
@@ -575,7 +650,7 @@ import { ComboBox } from "./widgets/combobox.js";
|
||||
requestAnimationFrame(() => {
|
||||
easyMDE.codemirror.refresh();
|
||||
const wideScreen = window.matchMedia("(min-width: 901px)").matches;
|
||||
if (wideScreen && !easyMDE.isSideBySideActive()) {
|
||||
if (wideScreen && easyMDE.isSideBySideActive() !== preferredEditorSideBySide()) {
|
||||
easyMDE.toggleSideBySide();
|
||||
}
|
||||
updateEditorChromeMetrics();
|
||||
@@ -1045,6 +1120,17 @@ import { ComboBox } from "./widgets/combobox.js";
|
||||
* post : A successful save refreshes page metadata and opens the stored page.
|
||||
*/
|
||||
async function savePage() {
|
||||
return savePageWithOptions();
|
||||
}
|
||||
|
||||
function savePageFromFullscreen() {
|
||||
savePageWithOptions({ keepEditing: true }).catch((error) => {
|
||||
$("save-status").textContent = error.message;
|
||||
});
|
||||
}
|
||||
|
||||
/** Save the current Markdown and optionally keep the editor active. */
|
||||
async function savePageWithOptions({ keepEditing = false } = {}) {
|
||||
const title = $("editor-title").value.trim();
|
||||
const namespace = $("editor-namespace").value.trim();
|
||||
const currentSlug = state.editingNew ? state.newPageSlug : state.currentPage?.slug;
|
||||
@@ -1096,8 +1182,14 @@ import { ComboBox } from "./widgets/combobox.js";
|
||||
applyTranslations();
|
||||
$("account-role").textContent = tr(`role-${state.session.user.role}`, state.session.user.role);
|
||||
}
|
||||
location.hash = `#/${encodeURIComponent(page.slug)}`;
|
||||
await openPage(page.slug);
|
||||
if (keepEditing) {
|
||||
$("editor-namespace").disabled = true;
|
||||
updateEditorSlugInfo();
|
||||
$("edit-summary").value = "";
|
||||
} else {
|
||||
location.hash = `#/${encodeURIComponent(page.slug)}`;
|
||||
await openPage(page.slug);
|
||||
}
|
||||
$("save-status").textContent = tr("saved", "Saved");
|
||||
} catch (error) {
|
||||
$("save-status").textContent = error.message;
|
||||
@@ -1189,7 +1281,7 @@ import { ComboBox } from "./widgets/combobox.js";
|
||||
}
|
||||
const result = await uploadOneFile(file);
|
||||
$("save-status").textContent = tr("image-upload-complete", "Image upload complete");
|
||||
return result.url;
|
||||
return encodeURI(result.url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1206,9 +1298,10 @@ import { ComboBox } from "./widgets/combobox.js";
|
||||
for (const file of fileList) {
|
||||
const result = await uploadOneFile(file);
|
||||
const escapedName = file.name.replace(/[\[\]]/g, "\\$&");
|
||||
const url = encodeURI(result.url);
|
||||
const markdown = isInlineImage(file)
|
||||
? ``
|
||||
: `[${escapedName}](${result.url})`;
|
||||
? ``
|
||||
: `[${escapedName}](${url})`;
|
||||
insertTextAtCursor(`\n${markdown}\n`);
|
||||
}
|
||||
$("save-status").textContent = tr("upload-complete", "Upload complete");
|
||||
@@ -2023,6 +2116,7 @@ import { ComboBox } from "./widgets/combobox.js";
|
||||
state.translationTemplate = translationData.template || "";
|
||||
state.translations = translationData.translations || {};
|
||||
applyTranslations();
|
||||
initializeSidebarToggle();
|
||||
await cmapWorkspace.initialize();
|
||||
initializeEditor();
|
||||
$("account-name").textContent = state.session.user.displayName;
|
||||
|
||||
@@ -182,6 +182,44 @@ function positionIsProtected(start, end, ranges) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Normalize link destinations that contain whitespace before Marked parses them. */
|
||||
function normalizeMarkdownLinkDestinations(markdown) {
|
||||
const lines = String(markdown || "").split("\n");
|
||||
const result = [];
|
||||
let fence = null;
|
||||
const linkPattern = /(!?\[[^\]\n]*\]\()([^\)\n]*)(\))/g;
|
||||
|
||||
for (const line of lines) {
|
||||
const fenceMatch = line.match(/^\s*(```+|~~~+)/);
|
||||
if (fenceMatch) {
|
||||
const marker = fenceMatch[1].charAt(0);
|
||||
fence = fence === null ? marker : (fence === marker ? null : fence);
|
||||
result.push(line);
|
||||
continue;
|
||||
}
|
||||
if (fence !== null || /^\s{4}/.test(line)) {
|
||||
result.push(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
const codeRanges = [];
|
||||
for (const match of line.matchAll(/`+[^`\n]*`+/g)) {
|
||||
codeRanges.push({ start: match.index, end: match.index + match[0].length });
|
||||
}
|
||||
result.push(line.replace(linkPattern, (match, before, body, after, offset) => {
|
||||
if (positionIsProtected(offset, offset + match.length, codeRanges)) return match;
|
||||
const trimmed = body.trim();
|
||||
if (!trimmed || trimmed.startsWith("<") || !/\s/.test(trimmed)) return match;
|
||||
|
||||
const titleMatch = trimmed.match(/^(.+?)(\s+(?:"[^"\n]*"|'[^'\n]*'|\([^\)\n]*\)))$/);
|
||||
const destination = titleMatch ? titleMatch[1] : trimmed;
|
||||
const title = titleMatch ? titleMatch[2] : "";
|
||||
return `${before}${encodeURI(destination)}${title}${after}`;
|
||||
}));
|
||||
}
|
||||
return result.join("\n");
|
||||
}
|
||||
|
||||
/** Prefix every unprotected CamelCase WikiWord with the literal escape marker. */
|
||||
function protectCamelCaseWikiWords(markdown) {
|
||||
const wikiWordPattern = /(?<![!\p{L}\p{N}._-])((?:\p{Lu}\p{Ll}+){2,})(?![\p{L}\p{N}_-])/gu;
|
||||
@@ -363,6 +401,7 @@ export {
|
||||
expandNamespacedMarkdownLinks,
|
||||
expandTodoMarkup,
|
||||
expandWikiMentions,
|
||||
normalizeMarkdownLinkDestinations,
|
||||
protectCamelCaseWikiWords,
|
||||
extractCmapEmbeds,
|
||||
restoreCmapEmbeds
|
||||
|
||||
Reference in New Issue
Block a user