fix: restore editor paste menu and PDF reflow - #266
Merged
alecdotdev merged 5 commits intoJul 29, 2026
Merged
Conversation
PathGao
marked this pull request as ready for review
July 28, 2026 09:43
PathGao
force-pushed
the
codex/fix-editor-context-menu-and-pdf
branch
from
July 29, 2026 07:06
a27e396 to
5ef7286
Compare
This was referenced Jul 29, 2026
Collaborator
Author
This was referenced Aug 1, 2026
Closed
PathGao
pushed a commit
that referenced
this pull request
Aug 8, 2026
Right-clicking in the editor and choosing Paste did nothing. The menu half of that report was fixed in #266; this is the clipboard itself, and the answer turned out to be smaller than the diagnosis. Markpad is a webview, not Electron, so Monaco's `platform.isNative` is false and its clipboard code takes the branch written for the web. There it decides whether to register a Paste action at all by asking whether the API *exists*: ```js const supportsPaste = (typeof navigator.clipboard === 'undefined' || isFirefox) ? document.queryCommandSupported('paste') : true; ``` `navigator.clipboard` is an object in any Chromium webview, so the action registers and the menu item is drawn — the check is never for permission. `readText()` then rejects, `BrowserClipboardService` catches it and returns `''`, and `if (clipboardText !== '')` declines to paste. No error, no feedback. wry leaves the webview's `clipboard` attribute off, which on Windows means the WebView2 `PermissionRequested` handler allowing CLIPBOARD_READ is never registered. Turning it on is the obvious fix, the plumbing is intact all the way down — `enable_clipboard_access()` → `webview_attributes.clipboard` → `.with_clipboard(...)` → `add_PermissionRequested(...)` — and **it does not work**. Built it, ran it on Windows, right-clicked Paste: still nothing. tauri-apps/tauri#12007 has been open since December 2024 on exactly this, and the answer the ecosystem gives is the one Markpad already had for ⌘V: go through Rust. The official `tauri-plugin-clipboard-manager` exists to be "an alternative to the native navigator.clipboard methods". Three operations had six implementations. ⌘X was the browser's, ⌘C and ⌘V were ours through Rust, and the three menu items were Monaco's — of which Paste cannot work in a webview at all, and Cut and Copy are dead on Linux, where the same wry default gates `set_javascript_can_access_clipboard`. Now: three functions, six entry points. ``` keyboard menu cut ⌘X ─┐ Cut ─┐ copy ⌘C ─┼→ cutToClipboard Copy ┼→ same three functions paste ⌘V ─┘ copyToClipboard Paste┘ pasteFromClipboard → invoke → arboard ``` Both halves are free to take. Monaco leaves ⌘X/⌘C/⌘V unbound in a browser by design ("browsers do that for us" — clipboard.js), so the key slots were available. And its editor context menu holds nothing else this app can use: `gotoSymbol` and `inlayHints` need language providers Markpad does not register, and Copy As / Share are empty in standalone. So `contextmenu: false`, and the menu the preview pane already had is drawn for the editor too — already translated six ways, already styled, and now the same on both sides. That also settles what #266 could not. Its fix was to stop covering Monaco's menu, which was right for the overlay bug and left the reader pointed at a menu whose Paste silently did nothing. Nothing here depends on a webview permission, and nothing imports a Monaco internal, so there is no upgrade that can quietly take it away. Three existing tests changed, each because the thing it described moved: - `issue261EditorPdf` pinned "Monaco must receive the event before the document menu prevents it". The invariant is now that the editor's branch runs before every other rule — including the carve-out that leaves text fields their native menu, which would otherwise claim every right-click in the editor, because Monaco takes input through a hidden `<textarea>`. - `editorOptionWiring` pinned the whole-line-on-empty-selection rule inside `custom-copy`. It lives in `clipboardTextForSelection` now, where cut reads it too — a cut has to remove exactly what a copy would have taken. A new assertion holds the line: `clipboard_write_text` may appear exactly twice in the component. - `imageUndoKeepsFile` located the paste body as "the `addCommand` callback that reads a clipboard image". It is a named function now, and the lookup asserts no inline paste has appeared beside it.
PathGao
added a commit
that referenced
this pull request
Aug 8, 2026
* fix(clipboard): one cut, one copy, one paste (#207) Right-clicking in the editor and choosing Paste did nothing. The menu half of that report was fixed in #266; this is the clipboard itself, and the answer turned out to be smaller than the diagnosis. Markpad is a webview, not Electron, so Monaco's `platform.isNative` is false and its clipboard code takes the branch written for the web. There it decides whether to register a Paste action at all by asking whether the API *exists*: ```js const supportsPaste = (typeof navigator.clipboard === 'undefined' || isFirefox) ? document.queryCommandSupported('paste') : true; ``` `navigator.clipboard` is an object in any Chromium webview, so the action registers and the menu item is drawn — the check is never for permission. `readText()` then rejects, `BrowserClipboardService` catches it and returns `''`, and `if (clipboardText !== '')` declines to paste. No error, no feedback. wry leaves the webview's `clipboard` attribute off, which on Windows means the WebView2 `PermissionRequested` handler allowing CLIPBOARD_READ is never registered. Turning it on is the obvious fix, the plumbing is intact all the way down — `enable_clipboard_access()` → `webview_attributes.clipboard` → `.with_clipboard(...)` → `add_PermissionRequested(...)` — and **it does not work**. Built it, ran it on Windows, right-clicked Paste: still nothing. tauri-apps/tauri#12007 has been open since December 2024 on exactly this, and the answer the ecosystem gives is the one Markpad already had for ⌘V: go through Rust. The official `tauri-plugin-clipboard-manager` exists to be "an alternative to the native navigator.clipboard methods". Three operations had six implementations. ⌘X was the browser's, ⌘C and ⌘V were ours through Rust, and the three menu items were Monaco's — of which Paste cannot work in a webview at all, and Cut and Copy are dead on Linux, where the same wry default gates `set_javascript_can_access_clipboard`. Now: three functions, six entry points. ``` keyboard menu cut ⌘X ─┐ Cut ─┐ copy ⌘C ─┼→ cutToClipboard Copy ┼→ same three functions paste ⌘V ─┘ copyToClipboard Paste┘ pasteFromClipboard → invoke → arboard ``` Both halves are free to take. Monaco leaves ⌘X/⌘C/⌘V unbound in a browser by design ("browsers do that for us" — clipboard.js), so the key slots were available. And its editor context menu holds nothing else this app can use: `gotoSymbol` and `inlayHints` need language providers Markpad does not register, and Copy As / Share are empty in standalone. So `contextmenu: false`, and the menu the preview pane already had is drawn for the editor too — already translated six ways, already styled, and now the same on both sides. That also settles what #266 could not. Its fix was to stop covering Monaco's menu, which was right for the overlay bug and left the reader pointed at a menu whose Paste silently did nothing. Nothing here depends on a webview permission, and nothing imports a Monaco internal, so there is no upgrade that can quietly take it away. Three existing tests changed, each because the thing it described moved: - `issue261EditorPdf` pinned "Monaco must receive the event before the document menu prevents it". The invariant is now that the editor's branch runs before every other rule — including the carve-out that leaves text fields their native menu, which would otherwise claim every right-click in the editor, because Monaco takes input through a hidden `<textarea>`. - `editorOptionWiring` pinned the whole-line-on-empty-selection rule inside `custom-copy`. It lives in `clipboardTextForSelection` now, where cut reads it too — a cut has to remove exactly what a copy would have taken. A new assertion holds the line: `clipboard_write_text` may appear exactly twice in the component. - `imageUndoKeepsFile` located the paste body as "the `addCommand` callback that reads a clipboard image". It is a named function now, and the lookup asserts no inline paste has appeared beside it. * fix(clipboard): keep the two editor-menu entries this app can use Drawing the editor's context menu ourselves took away everything Monaco used to contribute to it, and two of those worked: **Command Palette** and **Change All Occurrences**. Neither needs a language provider, so both were there in every build, and both disappeared. Found by looking, not by a test. The rest of what Monaco offers there — Go to Symbol, Quick Fix, Refactor, Format Document, Rename — is gated on providers Markdown has none of and never appeared in this app, which is what made the earlier claim that "nothing else is usable" wrong rather than merely imprecise. The audit that produced it searched for `MenuId.EditorContext`, and standalone actions register through `contextMenuOpts` instead. They come back translated, which is a gain rather than parity: Monaco's menu is English whatever language the app is in. `menu.commandPalette` already existed — the shortcuts pane uses it — so it is reused rather than declared a second time in the same object, where the later one would have silently won. `menu.changeAllOccurrences` is new, in all 26 languages. A missing key does not throw: `t()` falls back to English and then to the key itself, so a forgotten locale ships either an English label between translated siblings or the literal string `menu.changeAllOccurrences`. Its neighbours (cut, copy, paste, commandPalette) all carry 26, and the new test holds every label this menu asks for to that count. * fix(clipboard): copy plain text from the menu bar too (#393) Monaco writes a styled `text/html` flavour beside the plain text on a copy. Everything Markpad produces IS plain text, so pasting into Word or Outlook gave coloured monospace instead of the Markdown that was copied — the styled flavour has no audience here. Item 3 of the audit in #393; `false` is what that issue recommends. It was in the first draft of this branch and taken out when cut, copy and paste were collapsed onto three functions of our own, on the reasoning that Monaco's own copy had become unreachable. It had not. Those three cover ⌘X/⌘C/⌘V and the editor's context menu. macOS has a third way in that reaches none of them: Edit > Copy in the menu bar is a `PredefinedMenuItem::copy` (#527), which asks the WEBVIEW to perform its own copy. So without this option the menu bar puts a different clipboard on the pasteboard than the other two routes do, from the same selection. The test says that rather than saying the option is set, and asserts the menu bar route still exists — so if `PredefinedMenuItem::copy` ever goes, this comes up for review instead of sitting there as an option nobody remembers the reason for. * fix(clipboard): print the chord beside the two menu entries that need it Monaco's context menu showed a shortcut next to every item and ours showed none — a regression from drawing the menu ourselves, and the kind that is invisible until someone goes looking for a command they used to reach that way. Only the bottom two get one. Command Palette is `F1` and Change All Occurrences is `Mod+F2`, and for the second in particular the menu entry is most of how anyone learns the chord exists. Cut, copy and paste do not: those are OS conventions rather than app shortcuts — `shortcuts.ts` says so about the same three — and printing them costs a column of width in every language to tell people something they already know. `formatChord` rather than two literals, so the Mac and Windows spellings cannot drift apart. Confirmed against Monaco's own registrations: `KeyCode.F1` with no modifier, and `KeyMod.CtrlCmd | KeyCode.F2`. * fix(clipboard): leave the editor focused after a clipboard action Reported from Windows as "⌘Z stopped working". It had not: paste from the context menu inserted the text and left focus on the menu item, so there was no caret and the next keystroke went to the document instead of the editor. The visible symptom named a different feature than the broken one. ⌘X/⌘C/⌘V never needed a `focus()` — a keybinding fires with the editor focused by definition — which is exactly why it was missing once those same functions were given a second entry point. `cutToClipboard` was written fresh and had it; `pasteFromClipboard` was lifted whole out of the ⌘V command and carried the assumption with it. All three focus first now, matching `runEditorAction`, and the test asserts the order rather than the presence: focusing after the work leaves the same gap for anything that reads the selection. --------- Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #261
Tests:
npm testnpm run checkcargo test --quiet