diff --git a/static/app.css b/static/app.css index 325a377..c7f0ebe 100644 --- a/static/app.css +++ b/static/app.css @@ -325,6 +325,7 @@ body { .editor pre code { background: none; padding: 0; } .editor blockquote { border-left: 3px solid var(--secondary); margin: 0.5em 0; padding-left: 12px; color: var(--muted); } .editor a { color: var(--secondary); text-decoration: underline; cursor: pointer; } +.editor img { max-width: 100%; } .editor-split { display: grid; diff --git a/static/app.js b/static/app.js index b52ff79..bd89a33 100644 --- a/static/app.js +++ b/static/app.js @@ -1,6 +1,7 @@ import { Editor } from "https://esm.sh/@tiptap/core@2.11.5"; import StarterKit from "https://esm.sh/@tiptap/starter-kit@2.11.5"; import Link from "https://esm.sh/@tiptap/extension-link@2.11.5"; +import Image from "https://esm.sh/@tiptap/extension-image@2.11.5"; const root = document.getElementById("root"); const turndownService = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" }); @@ -467,8 +468,9 @@ function renderEditor(body, owner) { root.innerHTML = ""; state.editor?.destroy(); - const back = el("button", { class: "btn ghost", text: "← Back", onclick: showHome }); - const save = el("button", { class: "btn primary", text: "Save", onclick: saveNote }); + const isNew = !state.noteId; + const back = el("button", { class: "btn ghost", text: "← Back", onclick: exitEditor }); + const save = isNew ? null : el("button", { class: "btn primary", text: "Save", onclick: saveNote }); const del = state.noteId ? el("button", { class: "btn danger", text: "Delete", onclick: deleteNote }) : null; const hist = state.noteId ? el("button", { class: "btn ghost", text: "History", onclick: showHistory }) : null; @@ -511,7 +513,7 @@ function renderEditor(body, owner) { const attach = el("button", { class: "btn", text: "Attach", onclick: () => fileInput.click() }); const wrap = el("div", { class: "editor-wrap" }, [ - el("div", { class: "editor-topbar" }, [back, spaceSel, el("div", { class: "spacer" }), attach, hist, del, commonLabel, previewBtn, save]), + el("div", { class: "editor-topbar" }, [back, ...(isNew ? [] : [spaceSel]), el("div", { class: "spacer" }), attach, hist, del, commonLabel, previewBtn, ...(isNew ? [] : [save])]), toolbar, split, hint, @@ -522,7 +524,7 @@ function renderEditor(body, owner) { state.editor = new Editor({ element: editorArea, - extensions: [StarterKit, Link.configure({ openOnClick: false })], + extensions: [StarterKit, Link.configure({ openOnClick: false }), Image], content: marked.parse(body), autofocus: true, }); @@ -530,14 +532,13 @@ function renderEditor(body, owner) { function updatePreview() { const md = turndownService.turndown(state.editor.getHTML()); previewArea.innerHTML = marked.parse(md); + previewArea.querySelectorAll("a").forEach((a) => { + a.target = "_blank"; + a.rel = "noopener noreferrer"; + }); } state.editor.on("update", updatePreview); updatePreview(); - - previewArea.addEventListener("click", (e) => { - const a = e.target.closest("a"); - if (a) e.preventDefault(); - }); } function sep() { return el("span", { class: "sep" }); } @@ -588,6 +589,17 @@ async function saveNote() { if (isNew) showHome(); } +function editorIsEmpty() { + return !state.editor || state.editor.getText().trim() === ""; +} + +async function exitEditor() { + if (!state.noteId && !editorIsEmpty()) { + await persistNote(); + } + showHome(); +} + async function saveAndNewNote() { await persistNote(); openNote(null); @@ -653,7 +665,7 @@ document.addEventListener("keydown", (e) => { if (mod && e.key.toLowerCase() === "s") { e.preventDefault(); if (e.shiftKey) saveAndNewNote(); - else saveNote(); + else persistNote(); } } });