diff --git a/images/activity-bar-fold-m.svg b/images/activity-bar-fold-m.svg
new file mode 100644
index 0000000..0a44b6e
--- /dev/null
+++ b/images/activity-bar-fold-m.svg
@@ -0,0 +1,9 @@
+
diff --git a/media/preview.css b/media/preview.css
index cd9a46e..b7b43d9 100644
--- a/media/preview.css
+++ b/media/preview.css
@@ -1,9 +1,39 @@
:root {
color-scheme: light dark;
+ --sm-scrollbar-size: 10px;
+ --sm-scrollbar-track: transparent;
+ --sm-scrollbar-thumb: rgba(127, 127, 127, 0.38);
+ --sm-scrollbar-thumb-hover: rgba(127, 127, 127, 0.58);
}
* {
box-sizing: border-box;
+ scrollbar-color: var(--sm-scrollbar-thumb) var(--sm-scrollbar-track);
+ scrollbar-width: thin;
+}
+
+*::-webkit-scrollbar {
+ width: var(--sm-scrollbar-size);
+ height: var(--sm-scrollbar-size);
+}
+
+*::-webkit-scrollbar-track {
+ background: var(--sm-scrollbar-track);
+}
+
+*::-webkit-scrollbar-thumb {
+ min-height: 36px;
+ border: 2px solid var(--sm-scrollbar-track);
+ border-radius: 999px;
+ background-color: var(--sm-scrollbar-thumb);
+}
+
+*::-webkit-scrollbar-thumb:hover {
+ background-color: var(--sm-scrollbar-thumb-hover);
+}
+
+*::-webkit-scrollbar-corner {
+ background: var(--sm-scrollbar-track);
}
html,
@@ -38,6 +68,9 @@ body {
--sm-quote-text: var(--vscode-textBlockQuote-foreground);
--sm-table-header-bg: var(--vscode-editorWidget-background);
--sm-focus: var(--vscode-focusBorder);
+ --sm-scrollbar-track: color-mix(in srgb, var(--sm-page-bg) 94%, var(--sm-border));
+ --sm-scrollbar-thumb: color-mix(in srgb, var(--sm-muted) 44%, transparent);
+ --sm-scrollbar-thumb-hover: color-mix(in srgb, var(--sm-muted) 68%, var(--sm-border));
background: var(--sm-page-bg);
color: var(--sm-text);
@@ -1498,14 +1531,24 @@ body.sm-theme-high-contrast .diagram-block:not(.render-block-tone-light) {
white-space: pre;
}
-body.vscode-dark .code-block:not(.render-block-tone-light) .shiki,
-body.vscode-high-contrast .code-block:not(.render-block-tone-light) .shiki,
+body.sm-theme-system.vscode-dark .code-block:not(.render-block-tone-light) .shiki,
+body.sm-theme-system.vscode-high-contrast .code-block:not(.render-block-tone-light) .shiki,
+body.sm-theme-dark .code-block:not(.render-block-tone-light) .shiki,
+body.sm-theme-forest .code-block:not(.render-block-tone-light) .shiki,
+body.sm-theme-ink .code-block:not(.render-block-tone-light) .shiki,
+body.sm-theme-terminal .code-block:not(.render-block-tone-light) .shiki,
+body.sm-theme-high-contrast .code-block:not(.render-block-tone-light) .shiki,
.code-block.render-block-tone-dark .shiki {
color: var(--shiki-dark, var(--sm-render-block-text));
}
-body.vscode-dark .code-block:not(.render-block-tone-light) .shiki span,
-body.vscode-high-contrast .code-block:not(.render-block-tone-light) .shiki span,
+body.sm-theme-system.vscode-dark .code-block:not(.render-block-tone-light) .shiki span,
+body.sm-theme-system.vscode-high-contrast .code-block:not(.render-block-tone-light) .shiki span,
+body.sm-theme-dark .code-block:not(.render-block-tone-light) .shiki span,
+body.sm-theme-forest .code-block:not(.render-block-tone-light) .shiki span,
+body.sm-theme-ink .code-block:not(.render-block-tone-light) .shiki span,
+body.sm-theme-terminal .code-block:not(.render-block-tone-light) .shiki span,
+body.sm-theme-high-contrast .code-block:not(.render-block-tone-light) .shiki span,
.code-block.render-block-tone-dark .shiki span {
color: var(--shiki-dark, var(--sm-render-block-text));
}
@@ -1516,8 +1559,8 @@ body.vscode-high-contrast .code-block:not(.render-block-tone-light) .shiki span,
}
@media (prefers-color-scheme: dark) {
- body:not(.vscode-light) .code-block:not(.render-block-tone-light) .shiki,
- body:not(.vscode-light) .code-block:not(.render-block-tone-light) .shiki span {
+ body.sm-theme-system:not(.vscode-light) .code-block:not(.render-block-tone-light) .shiki,
+ body.sm-theme-system:not(.vscode-light) .code-block:not(.render-block-tone-light) .shiki span {
color: var(--shiki-dark, var(--sm-render-block-text));
}
}
diff --git a/media/wysiwyg/editor-runtime.ts b/media/wysiwyg/editor-runtime.ts
index a2d532c..cffa310 100644
--- a/media/wysiwyg/editor-runtime.ts
+++ b/media/wysiwyg/editor-runtime.ts
@@ -20,6 +20,7 @@ import { listener, listenerCtx } from "@milkdown/kit/plugin/listener";
import { clipboard } from "@milkdown/kit/plugin/clipboard";
import { history } from "@milkdown/kit/plugin/history";
import { trailing } from "@milkdown/kit/plugin/trailing";
+import { upload, uploadConfig } from "@milkdown/kit/plugin/upload";
import { $nodeSchema, $remark, callCommand, getMarkdown, insert, replaceAll, replaceRange } from "@milkdown/kit/utils";
import {
CODE_BLOCK_CLASSES,
@@ -64,18 +65,21 @@ type Payload = {
translations?: {
toolbar?: Record;
noHeadings?: string;
+ outlineRevealCurrent?: string;
+ outlineCollapse?: string;
copiedCode?: string;
copyCode?: string;
- codeTheme?: string;
- codeThemeAuto?: string;
- codeThemeLight?: string;
- codeThemeDark?: string;
- editLanguage?: string;
- mathEdit?: string;
- mathDone?: string;
- rawHtmlEscaped?: string;
- };
- };
+ codeTheme?: string;
+ codeThemeAuto?: string;
+ codeThemeLight?: string;
+ codeThemeDark?: string;
+ editLanguage?: string;
+ mathEdit?: string;
+ mathDone?: string;
+ rawHtmlEscaped?: string;
+ footnote?: string;
+ };
+};
const vscode = acquireVsCodeApi();
const payloadElement = document.getElementById("payload") as HTMLElement | null;
@@ -109,7 +113,8 @@ const visualLabels = {
editLanguage: translations.editLanguage || "Edit language",
mathEdit: translations.mathEdit || "Edit",
mathDone: translations.mathDone || "Done",
- rawHtmlEscaped: translations.rawHtmlEscaped || "Raw HTML escaped"
+ rawHtmlEscaped: translations.rawHtmlEscaped || "Raw HTML escaped",
+ footnote: translations.footnote || "Footnote"
};
const mathRenderOptions = { katexEnabled: payload.katexEnabled !== false };
const sourceEditor = mustElement("source-editor");
@@ -122,10 +127,18 @@ const sidePanelCollapseElement = document.getElementById("side-panel-collapse")
const outlineCurrentElement = document.getElementById("outline-current") as HTMLButtonElement | null;
const outlineElement = mustElement("outline");
const searchElement = mustElement("outline-search");
+const editorPanelElement = document.querySelector(".editor-panel") as HTMLElement | null;
+const previewPanelElement = document.querySelector(".preview-panel") as HTMLElement | null;
+const splitResizerElement = document.getElementById("split-resizer") as HTMLElement | null;
+const initialRuntimeState = readRuntimeState();
+const DEFAULT_SPLIT_RATIO = 0.5;
+const SPLIT_KEYBOARD_STEP = 0.03;
+const SPLIT_MIN_PANE_WIDTH = 240;
let currentMarkdown = payload.text || "";
let currentMode = normalizeMode(payload.mode || "source");
let currentLayout = normalizeLayout(payload.layout || "workbench");
+let splitRatio = normalizeSplitRatio(initialRuntimeState.splitRatio);
let previewState = normalizePreviewState(payload.preview);
let imageResources = normalizeImageResources(payload.imageResources);
let milkdownEditor: Editor | null = null;
@@ -143,6 +156,31 @@ let activeSourceSelection = { start: 0, end: 0 };
let sidePanelOpen = false;
let currentOutlineHeadings: PreviewState["headings"] = [];
let activeOutlineId = "";
+let hoverTooltipTimer: number | undefined;
+let hoverTooltipElement: HTMLElement | null = null;
+let hoverTooltipTarget: HTMLElement | null = null;
+let splitResizePointerId: number | null = null;
+const HOVER_TOOLTIP_TARGET_SELECTOR = [
+ "[data-hover-tooltip]",
+ ".toolbar-button",
+ ".toolbar-menu-button",
+ ".side-panel-toggle",
+ ".outline-tool",
+ ".outline-item",
+ ".visual-math-inline",
+ ".visual-footnote-reference",
+ ".visual-html-source",
+ ".mermaid-render-error",
+ `.${CODE_BLOCK_CLASSES.language}`,
+ `.${CODE_BLOCK_CLASSES.toneButton}`,
+ `.${CODE_BLOCK_CLASSES.copyButton}`
+].join(",");
+
+type UploadedMarkdownImage = { id?: string; name?: string; markdown: string };
+const pendingImageUploads = new Map void;
+ reject: (error: unknown) => void;
+}>();
async function boot(): Promise {
try {
@@ -151,6 +189,7 @@ async function boot(): Promise {
renderPreview();
renderSidePanels(currentMarkdown);
bindEvents();
+ applySplitRatio(false);
applyLayout();
setScriptState("runtime-ready", "ready");
post("ready");
@@ -205,6 +244,25 @@ function normalizeLayout(layout: string): string {
return ["workbench", "editorOnly", "splitEdit", "previewOnly"].includes(layout) ? layout : "workbench";
}
+function readRuntimeState(): Record {
+ const state = vscode.getState();
+ return state && typeof state === "object" ? state as Record : {};
+}
+
+function saveRuntimeState(update: Record): void {
+ vscode.setState({ ...readRuntimeState(), ...update });
+}
+
+function normalizeSplitRatio(value: unknown): number {
+ return typeof value === "number" && Number.isFinite(value)
+ ? clamp(value, 0.2, 0.8)
+ : DEFAULT_SPLIT_RATIO;
+}
+
+function clamp(value: number, min: number, max: number): number {
+ return Math.min(Math.max(value, min), max);
+}
+
function normalizePreviewState(value: unknown): PreviewState | null {
if (!value || typeof value !== "object") {
return null;
@@ -307,12 +365,12 @@ function renderToolbarItem(action: string): string {
}
function toolbarButton(action: string, title: string, icon: string): string {
- return ``;
+ return ``;
}
function toolbarMenu(action: string, title: string, icon: string, menuActions: string[], className: string): string {
return `