WIP: save Folio changes before OpenCode restart
This commit is contained in:
parent
11dd692ec4
commit
c7323af38f
8 changed files with 194 additions and 8 deletions
BIN
FolioAppIcon.png
Normal file
BIN
FolioAppIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 683 KiB |
|
|
@ -1,4 +1,4 @@
|
||||||
import { app, BrowserWindow, dialog, ipcMain, shell } from "electron";
|
import { app, BrowserWindow, clipboard, dialog, ipcMain, shell } from "electron";
|
||||||
import type { OpenDialogOptions, SaveDialogOptions } from "electron";
|
import type { OpenDialogOptions, SaveDialogOptions } from "electron";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
@ -68,7 +68,8 @@ function createWindow() {
|
||||||
const scenarioPath = path.join(__dirname, "..", "tests", "selftest-scenario.js");
|
const scenarioPath = path.join(__dirname, "..", "tests", "selftest-scenario.js");
|
||||||
const scenario = fs
|
const scenario = fs
|
||||||
.readFileSync(scenarioPath, "utf-8")
|
.readFileSync(scenarioPath, "utf-8")
|
||||||
.replace(/__BOOKDIR_VALUE__/, JSON.stringify(bookDir));
|
.replace(/__BOOKDIR_VALUE__/, JSON.stringify(bookDir))
|
||||||
|
.replace(/__BOOKDIR2_VALUE__/, JSON.stringify(process.env.FOLIO_TEST_BOOK2 || ""));
|
||||||
mainWindow?.webContents
|
mainWindow?.webContents
|
||||||
.executeJavaScript(scenario, false)
|
.executeJavaScript(scenario, false)
|
||||||
.then((summary) => {
|
.then((summary) => {
|
||||||
|
|
@ -124,7 +125,12 @@ function forwardEditShortcuts(win: BrowserWindow) {
|
||||||
const mod = input.control || input.meta;
|
const mod = input.control || input.meta;
|
||||||
if (!mod) return;
|
if (!mod) return;
|
||||||
const key = input.key.toLowerCase();
|
const key = input.key.toLowerCase();
|
||||||
if (key === "z" && !input.shift) {
|
if (key === "a" && !input.shift && !input.alt) {
|
||||||
|
// Route Ctrl+A to the renderer so a read-only CodeMirror view (Full Book)
|
||||||
|
// can select its whole document instead of the native page-wide selectAll.
|
||||||
|
event.preventDefault();
|
||||||
|
win.webContents.send("folio:selectAll-requested");
|
||||||
|
} else if (key === "z" && !input.shift) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
win.webContents.send("folio:undo-requested");
|
win.webContents.send("folio:undo-requested");
|
||||||
} else if ((key === "z" && input.shift) || (key === "y" && !input.shift)) {
|
} else if ((key === "z" && input.shift) || (key === "y" && !input.shift)) {
|
||||||
|
|
@ -323,6 +329,15 @@ ipcMain.handle("folio:saveChapter", (_e, id: string, content: string) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("folio:copyText", (_e, text: string) => {
|
||||||
|
clipboard.writeText(typeof text === "string" ? text : "");
|
||||||
|
return { ok: true };
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("folio:getClipboardText", () => {
|
||||||
|
return clipboard.readText();
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle("folio:importAttachment", async () => {
|
ipcMain.handle("folio:importAttachment", async () => {
|
||||||
const bp = requireBook();
|
const bp = requireBook();
|
||||||
const { canceled, filePaths } = await showOpenDialog({
|
const { canceled, filePaths } = await showOpenDialog({
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,11 @@ export function buildMenu(
|
||||||
{ role: "copy" },
|
{ role: "copy" },
|
||||||
{ role: "paste" },
|
{ role: "paste" },
|
||||||
{ type: "separator" },
|
{ type: "separator" },
|
||||||
{ role: "selectAll" },
|
{
|
||||||
|
label: "Select All",
|
||||||
|
accelerator: "CmdOrCtrl+A",
|
||||||
|
click: () => getWindow()?.webContents.send("folio:selectAll-requested"),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ contextBridge.exposeInMainWorld("folio", {
|
||||||
exportMarkdown: () => ipcRenderer.invoke("folio:exportMarkdown"),
|
exportMarkdown: () => ipcRenderer.invoke("folio:exportMarkdown"),
|
||||||
exportZip: () => ipcRenderer.invoke("folio:exportZip"),
|
exportZip: () => ipcRenderer.invoke("folio:exportZip"),
|
||||||
importAttachment: () => ipcRenderer.invoke("folio:importAttachment"),
|
importAttachment: () => ipcRenderer.invoke("folio:importAttachment"),
|
||||||
|
copyText: (text: string) => ipcRenderer.invoke("folio:copyText", text),
|
||||||
|
getClipboardText: () => ipcRenderer.invoke("folio:getClipboardText"),
|
||||||
|
|
||||||
// events
|
// events
|
||||||
onBookOpened: (cb: (data: { path: string; title: string }) => void) =>
|
onBookOpened: (cb: (data: { path: string; title: string }) => void) =>
|
||||||
|
|
@ -52,4 +54,6 @@ contextBridge.exposeInMainWorld("folio", {
|
||||||
ipcRenderer.on("folio:undo-requested", () => cb()),
|
ipcRenderer.on("folio:undo-requested", () => cb()),
|
||||||
onRedoRequested: (cb: () => void) =>
|
onRedoRequested: (cb: () => void) =>
|
||||||
ipcRenderer.on("folio:redo-requested", () => cb()),
|
ipcRenderer.on("folio:redo-requested", () => cb()),
|
||||||
|
onSelectAllRequested: (cb: () => void) =>
|
||||||
|
ipcRenderer.on("folio:selectAll-requested", () => cb()),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import type { BookMeta } from "../main/project.js";
|
||||||
import { alertMessage, confirmMessage, promptInput } from "./dialog.js";
|
import { alertMessage, confirmMessage, promptInput } from "./dialog.js";
|
||||||
import { createMdEditor, editorText, setEditorText } from "./md-editor.js";
|
import { createMdEditor, editorText, setEditorText } from "./md-editor.js";
|
||||||
import type { EditorView } from "@codemirror/view";
|
import type { EditorView } from "@codemirror/view";
|
||||||
import { redo, undo, indentLess, indentMore } from "@codemirror/commands";
|
import { redo, selectAll, undo, indentLess, indentMore } from "@codemirror/commands";
|
||||||
import {
|
import {
|
||||||
applyFormatPainter,
|
applyFormatPainter,
|
||||||
captureFormat,
|
captureFormat,
|
||||||
|
|
@ -54,6 +54,8 @@ export type FolioAPI = {
|
||||||
exportMarkdown: () => Promise<any>;
|
exportMarkdown: () => Promise<any>;
|
||||||
exportZip: () => Promise<any>;
|
exportZip: () => Promise<any>;
|
||||||
importAttachment: () => Promise<any>;
|
importAttachment: () => Promise<any>;
|
||||||
|
copyText: (text: string) => Promise<any>;
|
||||||
|
getClipboardText: () => Promise<string>;
|
||||||
onBookOpened: (cb: (data: { path: string; title: string }) => void) => void;
|
onBookOpened: (cb: (data: { path: string; title: string }) => void) => void;
|
||||||
onBookClosed: (cb: () => void) => void;
|
onBookClosed: (cb: () => void) => void;
|
||||||
onChaptersChanged: (cb: () => void) => void;
|
onChaptersChanged: (cb: () => void) => void;
|
||||||
|
|
@ -65,6 +67,7 @@ export type FolioAPI = {
|
||||||
onExportZipRequested: (cb: () => void) => void;
|
onExportZipRequested: (cb: () => void) => void;
|
||||||
onUndoRequested: (cb: () => void) => void;
|
onUndoRequested: (cb: () => void) => void;
|
||||||
onRedoRequested: (cb: () => void) => void;
|
onRedoRequested: (cb: () => void) => void;
|
||||||
|
onSelectAllRequested: (cb: () => void) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const $ = (id: string) => document.getElementById(id) as HTMLElement;
|
const $ = (id: string) => document.getElementById(id) as HTMLElement;
|
||||||
|
|
@ -106,6 +109,18 @@ export function initBook(api: FolioAPI) {
|
||||||
const exportZipBtn = $("exportZipBtn") as HTMLButtonElement;
|
const exportZipBtn = $("exportZipBtn") as HTMLButtonElement;
|
||||||
const addChapterBtn = $("addChapterBtn") as HTMLButtonElement;
|
const addChapterBtn = $("addChapterBtn") as HTMLButtonElement;
|
||||||
const projectBar = $("projectBar");
|
const projectBar = $("projectBar");
|
||||||
|
const fullBookNav = $("fullBookNav");
|
||||||
|
const fullBookBtn = $("fullBookBtn") as HTMLButtonElement;
|
||||||
|
const fullBookView = $("fullBookView");
|
||||||
|
const fullBookHost = $("fullBookHost");
|
||||||
|
const fullBookStats = $("fullBookStats");
|
||||||
|
const copyAllBtn = $("copyAllBtn") as HTMLButtonElement;
|
||||||
|
const fullBookStatus = $("fullBookStatus");
|
||||||
|
|
||||||
|
const fullBookEditor: EditorView = createMdEditor(fullBookHost, {
|
||||||
|
onChange: () => undefined,
|
||||||
|
readOnly: true,
|
||||||
|
});
|
||||||
|
|
||||||
const editorView: EditorView = createMdEditor(editorHost, {
|
const editorView: EditorView = createMdEditor(editorHost, {
|
||||||
onChange: (text) => handleEditorChange(text),
|
onChange: (text) => handleEditorChange(text),
|
||||||
|
|
@ -325,6 +340,7 @@ export function initBook(api: FolioAPI) {
|
||||||
function setChapterButtons(enabled: boolean) {
|
function setChapterButtons(enabled: boolean) {
|
||||||
addChapterBtn.disabled = !enabled;
|
addChapterBtn.disabled = !enabled;
|
||||||
newChapterBtn.disabled = !enabled;
|
newChapterBtn.disabled = !enabled;
|
||||||
|
fullBookNav.classList.toggle("hidden", !enabled);
|
||||||
}
|
}
|
||||||
let dirty = false;
|
let dirty = false;
|
||||||
let loadedContent = "";
|
let loadedContent = "";
|
||||||
|
|
@ -338,6 +354,7 @@ export function initBook(api: FolioAPI) {
|
||||||
function showWelcome() {
|
function showWelcome() {
|
||||||
welcome.classList.remove("hidden");
|
welcome.classList.remove("hidden");
|
||||||
chapterView.classList.add("hidden");
|
chapterView.classList.add("hidden");
|
||||||
|
fullBookView.classList.add("hidden");
|
||||||
editorPlaceholder.classList.add("hidden");
|
editorPlaceholder.classList.add("hidden");
|
||||||
projectBar.classList.add("hidden");
|
projectBar.classList.add("hidden");
|
||||||
currentBook = null;
|
currentBook = null;
|
||||||
|
|
@ -345,11 +362,15 @@ export function initBook(api: FolioAPI) {
|
||||||
ordered = [];
|
ordered = [];
|
||||||
selectedId = null;
|
selectedId = null;
|
||||||
setChapterButtons(false);
|
setChapterButtons(false);
|
||||||
|
fullBookBtn.classList.remove("active");
|
||||||
|
setEditorText(fullBookEditor, "");
|
||||||
renderChapterList();
|
renderChapterList();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPlaceholder() {
|
function showPlaceholder() {
|
||||||
chapterView.classList.add("hidden");
|
chapterView.classList.add("hidden");
|
||||||
|
fullBookView.classList.add("hidden");
|
||||||
|
fullBookBtn.classList.remove("active");
|
||||||
editorPlaceholder.classList.remove("hidden");
|
editorPlaceholder.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -377,9 +398,11 @@ export function initBook(api: FolioAPI) {
|
||||||
}
|
}
|
||||||
setChapterButtons(!!meta);
|
setChapterButtons(!!meta);
|
||||||
renderChapterList();
|
renderChapterList();
|
||||||
|
if (fullBookView.classList.contains("hidden")) {
|
||||||
if (ordered.length) showPlaceholder();
|
if (ordered.length) showPlaceholder();
|
||||||
else showPlaceholder();
|
else showPlaceholder();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshMeta() {
|
async function refreshMeta() {
|
||||||
await loadMeta();
|
await loadMeta();
|
||||||
|
|
@ -387,6 +410,7 @@ export function initBook(api: FolioAPI) {
|
||||||
selectedId = null;
|
selectedId = null;
|
||||||
showPlaceholder();
|
showPlaceholder();
|
||||||
}
|
}
|
||||||
|
await refreshFullBook();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderChapterList() {
|
function renderChapterList() {
|
||||||
|
|
@ -456,6 +480,8 @@ export function initBook(api: FolioAPI) {
|
||||||
editorView.dispatch({ selection: { anchor: text.length } });
|
editorView.dispatch({ selection: { anchor: text.length } });
|
||||||
editorView.focus();
|
editorView.focus();
|
||||||
clearTimers();
|
clearTimers();
|
||||||
|
fullBookBtn.classList.remove("active");
|
||||||
|
fullBookView.classList.add("hidden");
|
||||||
chapterView.classList.remove("hidden");
|
chapterView.classList.remove("hidden");
|
||||||
editorPlaceholder.classList.add("hidden");
|
editorPlaceholder.classList.add("hidden");
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -463,6 +489,69 @@ export function initBook(api: FolioAPI) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- full book view ----
|
||||||
|
|
||||||
|
function chapterHasOwnHeading(content: string, title: string): boolean {
|
||||||
|
const t = title.trim().toLowerCase();
|
||||||
|
if (!t) return true;
|
||||||
|
for (const line of content.split(/\r?\n/)) {
|
||||||
|
const m = line.match(/^#{1,6}\s+(.+?)\s*$/);
|
||||||
|
if (m && m[1].trim().toLowerCase() === t) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assembleFullBook(): Promise<string> {
|
||||||
|
const parts: string[] = [];
|
||||||
|
for (const ch of ordered) {
|
||||||
|
const res = await api.getChapterContent(ch.id);
|
||||||
|
const content = typeof res === "string" ? res : "";
|
||||||
|
const body = content.replace(/^\n+/, "").replace(/\s+$/, "");
|
||||||
|
const block: string[] = [];
|
||||||
|
if (!chapterHasOwnHeading(content, ch.title)) {
|
||||||
|
block.push(`# ${ch.title}`);
|
||||||
|
if (body) {
|
||||||
|
block.push("");
|
||||||
|
block.push(body);
|
||||||
|
}
|
||||||
|
} else if (body) {
|
||||||
|
block.push(body);
|
||||||
|
}
|
||||||
|
if (block.length) parts.push(block.join("\n"));
|
||||||
|
}
|
||||||
|
return parts.join("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderFullBook() {
|
||||||
|
const text = await assembleFullBook();
|
||||||
|
setEditorText(fullBookEditor, text);
|
||||||
|
fullBookStats.textContent = `words: ${countWords(text)} · chars: ${text.length}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectFullBook() {
|
||||||
|
if (selectedId && dirty) {
|
||||||
|
const res = await api.saveChapter(selectedId, editorText(editorView));
|
||||||
|
if (!(res && "error" in res)) loadedContent = editorText(editorView);
|
||||||
|
dirty = false;
|
||||||
|
saveStateEl.textContent = "Saved";
|
||||||
|
saveStateEl.classList.remove("unsaved");
|
||||||
|
}
|
||||||
|
clearTimers();
|
||||||
|
selectedId = null;
|
||||||
|
chapterList.querySelectorAll(".chapter-row").forEach((r) => r.classList.remove("active"));
|
||||||
|
fullBookBtn.classList.add("active");
|
||||||
|
chapterView.classList.add("hidden");
|
||||||
|
editorPlaceholder.classList.add("hidden");
|
||||||
|
fullBookView.classList.remove("hidden");
|
||||||
|
await renderFullBook();
|
||||||
|
fullBookEditor.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshFullBook() {
|
||||||
|
if (fullBookView.classList.contains("hidden")) return;
|
||||||
|
await renderFullBook();
|
||||||
|
}
|
||||||
|
|
||||||
function clearTimers() {
|
function clearTimers() {
|
||||||
if (saveTimer) { clearTimeout(saveTimer); saveTimer = null; }
|
if (saveTimer) { clearTimeout(saveTimer); saveTimer = null; }
|
||||||
}
|
}
|
||||||
|
|
@ -716,6 +805,25 @@ export function initBook(api: FolioAPI) {
|
||||||
addChapterBtn.addEventListener("click", () => void create());
|
addChapterBtn.addEventListener("click", () => void create());
|
||||||
exportBtn.addEventListener("click", () => void exportMarkdown());
|
exportBtn.addEventListener("click", () => void exportMarkdown());
|
||||||
exportZipBtn.addEventListener("click", () => void exportZip());
|
exportZipBtn.addEventListener("click", () => void exportZip());
|
||||||
|
fullBookBtn.addEventListener("click", () => void selectFullBook());
|
||||||
|
copyAllBtn.addEventListener("click", async () => {
|
||||||
|
const text = editorText(fullBookEditor);
|
||||||
|
if (!text) {
|
||||||
|
fullBookStatus.textContent = "Nothing to copy";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await api.copyText(text);
|
||||||
|
if (res && "error" in res) {
|
||||||
|
await alertMessage("Could not copy", (res as { error: string }).error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fullBookStatus.textContent = "Full book copied";
|
||||||
|
fullBookStatus.classList.add("unsaved");
|
||||||
|
setTimeout(() => {
|
||||||
|
fullBookStatus.textContent = "";
|
||||||
|
fullBookStatus.classList.remove("unsaved");
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
$("revealBtn").addEventListener("click", () => {
|
$("revealBtn").addEventListener("click", () => {
|
||||||
if (currentBook) void api.revealInFolder(currentBook.path);
|
if (currentBook) void api.revealInFolder(currentBook.path);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,40 @@ html, body {
|
||||||
border-top: 1px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- full book nav ---- */
|
||||||
|
.full-book-nav {
|
||||||
|
margin-top: var(--gap);
|
||||||
|
padding-top: var(--gap);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-book-btn {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-text);
|
||||||
|
font-family: var(--font-serif);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
padding: 8px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-book-btn:hover {
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: #fff;
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-book-btn.active {
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@
|
||||||
<button class="icon-button" id="revealBtn" title="Reveal in folder">📁</button>
|
<button class="icon-button" id="revealBtn" title="Reveal in folder">📁</button>
|
||||||
</div>
|
</div>
|
||||||
<ul class="chapter-list" id="chapterList"></ul>
|
<ul class="chapter-list" id="chapterList"></ul>
|
||||||
|
<div class="full-book-nav hidden" id="fullBookNav">
|
||||||
|
<button class="full-book-btn" id="fullBookBtn" title="View the entire book as one continuous manuscript">Full Book</button>
|
||||||
|
</div>
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
<button class="button button-ghost" id="newChapterBtn">+ New Chapter</button>
|
<button class="button button-ghost" id="newChapterBtn">+ New Chapter</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -161,6 +164,17 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-panes" id="editorHost"></div>
|
<div class="editor-panes" id="editorHost"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="full-book-view hidden" id="fullBookView">
|
||||||
|
<header class="chapter-header">
|
||||||
|
<h2 class="chapter-title">Full Book</h2>
|
||||||
|
<div class="editor-actions">
|
||||||
|
<span class="word-count" id="fullBookStats">words: 0 · chars: 0</span>
|
||||||
|
<button class="button button-primary" id="copyAllBtn" title="Copy the entire assembled manuscript as Markdown">Copy All</button>
|
||||||
|
<span class="save-state" id="fullBookStatus"></span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="editor-panes" id="fullBookHost"></div>
|
||||||
|
</div>
|
||||||
<div class="editor-placeholder hidden" id="editorPlaceholder">
|
<div class="editor-placeholder hidden" id="editorPlaceholder">
|
||||||
<p>Select a chapter to begin writing.</p>
|
<p>Select a chapter to begin writing.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import { toggleBold, toggleItalic, toggleLink, toggleStrikethrough, toggleUnderl
|
||||||
export interface MdEditorCallbacks {
|
export interface MdEditorCallbacks {
|
||||||
onChange: (text: string) => void;
|
onChange: (text: string) => void;
|
||||||
onSelection?: (view: EditorView) => void;
|
onSelection?: (view: EditorView) => void;
|
||||||
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatBindings: KeyBinding[] = [
|
const formatBindings: KeyBinding[] = [
|
||||||
|
|
@ -101,6 +102,7 @@ const folioTheme = EditorView.theme({
|
||||||
});
|
});
|
||||||
|
|
||||||
export function createMdEditor(host: HTMLElement, callbacks: MdEditorCallbacks): EditorView {
|
export function createMdEditor(host: HTMLElement, callbacks: MdEditorCallbacks): EditorView {
|
||||||
|
const readOnly = !!callbacks.readOnly;
|
||||||
const view = new EditorView({
|
const view = new EditorView({
|
||||||
state: EditorState.create({
|
state: EditorState.create({
|
||||||
doc: "",
|
doc: "",
|
||||||
|
|
@ -117,7 +119,12 @@ export function createMdEditor(host: HTMLElement, callbacks: MdEditorCallbacks):
|
||||||
search({ top: true }),
|
search({ top: true }),
|
||||||
syntaxHighlighting(mdHighlight),
|
syntaxHighlighting(mdHighlight),
|
||||||
folioTheme,
|
folioTheme,
|
||||||
keymap.of([...formatBindings, ...defaultKeymap, ...historyKeymap, ...searchKeymap, indentWithTab]),
|
...(readOnly ? [EditorState.readOnly.of(true)] : []),
|
||||||
|
keymap.of(
|
||||||
|
readOnly
|
||||||
|
? [...defaultKeymap, ...searchKeymap]
|
||||||
|
: [...formatBindings, ...defaultKeymap, ...historyKeymap, ...searchKeymap, indentWithTab]
|
||||||
|
),
|
||||||
EditorView.updateListener.of((update) => {
|
EditorView.updateListener.of((update) => {
|
||||||
if (update.docChanged) callbacks.onChange(update.state.doc.toString());
|
if (update.docChanged) callbacks.onChange(update.state.doc.toString());
|
||||||
if (callbacks.onSelection && (update.docChanged || update.selectionSet)) {
|
if (callbacks.onSelection && (update.docChanged || update.selectionSet)) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue