Sort a notebook, import Markdown back, and stop the page shrinking in Draw
The paper measurably shrank on switching to Draw — 733px to 460px on the same screen. The editor was a flex item at its default `flex: 0 1 auto`, so its width was its widest row's: the toolbar. Swapping a dozen formatting buttons for six drawing ones collapsed it. It now fills the pane, so the width is a property of the window. Each notebook remembers how its notes are ordered, on the server rather than per device. The manual order has to survive being looked away from, so `reindex` renumbers every note in the notebook rather than the filtered view, and dragging is switched off while a search narrows the list. Reordering has its own route: `PUT /notes/:id` would bump `updated_at` and snapshot a version, so a drag would move a note to the top of "last changed" and spend a slot of its history. Markdown and our own ZIP archives come back as notes, with their images re-uploaded as attachments. A drawing returns as the PNG it was exported as — vectors are not recoverable from pixels — and the import says so rather than leaving it to be discovered. Writing the parser turned up a real bug in the exporter: blocks were emitted with no blank line between them, so a list after a list became one list and a quote after an item was read as part of it. Creating or importing from "All Notes" now asks which notebook instead of silently taking the first one. The web-link prompt is a real dialog: a bare domain becomes https, javascript: is refused with a reason. The PDF viewer's hide-drawing control is a labelled button rather than a filled-or-hollow dot, and the export icon points down into a tray like every other download. 186 unit tests, 179 browser checks across eleven suites, including a new `offline` suite that loads the app cold with the network off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3acb8b66a4
commit
0a0864c58b
27 files changed
+2962
-86
No files matched your search
@@ -1,5 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { LinkDialog } from "../ui/LinkDialog.jsx";
|
||||
|
||||
/* Icons the main app does not already provide, drawn to match its stroke style. */
|
||||
const strokeProps = {
|
||||
width: 15,
|
||||
@@ -51,9 +53,10 @@ const AtIcon = () => (
|
||||
* Reads its active states straight off the editor, so the buttons reflect the
|
||||
* cursor even when formatting changed by keyboard shortcut.
|
||||
*/
|
||||
export function EditorToolbar({ editor, T, icons, compact }) {
|
||||
export function EditorToolbar({ editor, T, icons, compact, dark }) {
|
||||
const [, force] = useState(0);
|
||||
const [showTable, setShowTable] = useState(false);
|
||||
const [linkDialog, setLinkDialog] = useState(null);
|
||||
|
||||
// ProseMirror state changes do not go through React; subscribe so the active
|
||||
// states stay in sync with the cursor.
|
||||
@@ -111,19 +114,12 @@ export function EditorToolbar({ editor, T, icons, compact }) {
|
||||
cursor: "pointer",
|
||||
};
|
||||
|
||||
const promptLink = () => {
|
||||
const previous = editor.getAttributes("link").href || "";
|
||||
const url = window.prompt("Link URL (leave empty to remove)", previous);
|
||||
if (url === null) return;
|
||||
if (url === "") {
|
||||
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
||||
return;
|
||||
}
|
||||
if (!/^(https?|mailto):/i.test(url)) {
|
||||
window.alert("Only http(s) and mailto links are allowed.");
|
||||
return;
|
||||
}
|
||||
editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
|
||||
/* The dialog is opened rather than a prompt shown; the validation and the
|
||||
* "remove" affordance live inside it. See LinkDialog for why. */
|
||||
const openLinkDialog = () => setLinkDialog({ url: editor.getAttributes("link").href || "" });
|
||||
const closeLinkDialog = () => {
|
||||
setLinkDialog(null);
|
||||
editor.chain().focus().run(); // give the caret back to the note
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -259,9 +255,24 @@ export function EditorToolbar({ editor, T, icons, compact }) {
|
||||
>
|
||||
<AtIcon />
|
||||
</button>
|
||||
<button style={btn(editor.isActive("link"))} title="Web link" onClick={promptLink}>
|
||||
<button style={btn(editor.isActive("link"))} title="Web link" data-link-button onClick={openLinkDialog}>
|
||||
<LinkIcon />
|
||||
</button>
|
||||
{linkDialog && (
|
||||
<LinkDialog
|
||||
initialUrl={linkDialog.url}
|
||||
dark={dark}
|
||||
onClose={closeLinkDialog}
|
||||
onRemove={() => {
|
||||
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
||||
setLinkDialog(null);
|
||||
}}
|
||||
onSubmit={(href) => {
|
||||
editor.chain().focus().extendMarkRange("link").setLink({ href }).run();
|
||||
setLinkDialog(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!compact && (
|
||||
<>
|
||||
|
||||
Reference in new issue
Block a user