feat: add configurable paste mode#162
Conversation
Adds a "Paste mode" setting in Appearance → Editing that controls how pasted text is handled: - Markdown (default): detects and renders markdown syntax as before - Plain text: inserts without any markdown processing - Code block: wraps the pasted content in a fenced ``` code block The setting persists to .scratch/settings.json alongside other editor settings.
📝 WalkthroughWalkthroughThis PR adds a user-configurable "paste mode" setting that lets users choose how pasted content is handled in the editor. The feature includes type definitions, theme context state management with backend persistence, editor paste handler branching, and UI controls for mode selection. ChangesPaste Mode Feature
Sequence DiagramsequenceDiagram
participant User
participant SettingsUI as Settings UI
participant ThemeContext
participant Backend
participant Editor
User->>SettingsUI: select paste mode
SettingsUI->>ThemeContext: setPasteMode(mode)
ThemeContext->>Backend: persist pasteMode
Backend-->>ThemeContext: confirmation
Note over Editor,ThemeContext: Later when user pastes...
User->>Editor: paste content
Editor->>ThemeContext: useTheme() reads pasteMode
alt pasteMode == "plain"
Editor->>Editor: insert as plain text
else pasteMode == "code-block"
Editor->>Editor: parse as fenced code block
else pasteMode == "markdown"
Editor->>Editor: detect & parse markdown
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/editor/Editor.tsx`:
- Around line 1232-1247: When pasteMode === "code-block", ensure a deterministic
fallback inserts a codeBlock and returns true even if manager is missing or
manager.parse throws; if parsing succeeds keep existing behavior
(currentEditor.commands.insertContent(parsed)); otherwise normalize the pasted
text (use a variable e.g. normalized = text.replace(/\r\n?/g, '\n') or similar),
then call currentEditor.chain().focus().insertContent({ type: "codeBlock",
attrs: { language: null }, content: normalized ? [{ type: "text", text:
normalized }] : [] }).run(); finally return true so paste handling stays in
code-block mode instead of falling back to default.
In `@src/context/ThemeContext.tsx`:
- Around line 558-563: The setPasteMode setter currently does a
read-modify-write by calling getSettings() then updateSettings({...settings,
pasteMode: mode}), which can clobber concurrent changes; change setPasteMode
(and related callers) to persist only the changed field atomically by calling an
API that accepts a partial update (e.g., updateSettings({ pasteMode: mode }) or
a new updateSettingField('pasteMode', mode)) instead of spreading the full
settings object, or implement a versioned/conditional write in updateSettings to
prevent lost updates; keep setPasteModeState(mode) locally, await the atomic
patch/field-update call (or handle version conflicts) and preserve the existing
error handling in the catch block.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5c32f4ed-517a-42ca-9956-f655eae95510
📒 Files selected for processing (4)
src/components/editor/Editor.tsxsrc/components/settings/EditorSettingsSection.tsxsrc/context/ThemeContext.tsxsrc/types/note.ts
| if (pasteMode === "code-block") { | ||
| const manager = currentEditor.storage.markdown?.manager; | ||
| if (manager && typeof manager.parse === "function") { | ||
| try { | ||
| const fenced = "```\n" + text + "\n```"; | ||
| const parsed = manager.parse(fenced); | ||
| if (parsed) { | ||
| currentEditor.commands.insertContent(parsed); | ||
| return true; | ||
| } | ||
| } catch { | ||
| // fall through to default | ||
| } | ||
| } | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Guarantee code-block mode even when markdown parse fails.
If parse fails (or manager is unavailable), this returns false, so default paste behavior runs instead of code-block mode. Add a deterministic fallback that inserts a codeBlock node and still returns true.
Suggested fix
if (pasteMode === "code-block") {
+ const normalized = text.replace(/\r\n/g, "\n");
const manager = currentEditor.storage.markdown?.manager;
if (manager && typeof manager.parse === "function") {
try {
- const fenced = "```\n" + text + "\n```";
+ const fenced = "```\n" + normalized + "\n```";
const parsed = manager.parse(fenced);
if (parsed) {
currentEditor.commands.insertContent(parsed);
return true;
}
} catch {
// fall through to default
}
}
- return false;
+ currentEditor
+ .chain()
+ .focus()
+ .insertContent({
+ type: "codeBlock",
+ attrs: { language: null },
+ content: normalized ? [{ type: "text", text: normalized }] : [],
+ })
+ .run();
+ return true;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (pasteMode === "code-block") { | |
| const manager = currentEditor.storage.markdown?.manager; | |
| if (manager && typeof manager.parse === "function") { | |
| try { | |
| const fenced = "```\n" + text + "\n```"; | |
| const parsed = manager.parse(fenced); | |
| if (parsed) { | |
| currentEditor.commands.insertContent(parsed); | |
| return true; | |
| } | |
| } catch { | |
| // fall through to default | |
| } | |
| } | |
| return false; | |
| } | |
| if (pasteMode === "code-block") { | |
| const normalized = text.replace(/\r\n/g, "\n"); | |
| const manager = currentEditor.storage.markdown?.manager; | |
| if (manager && typeof manager.parse === "function") { | |
| try { | |
| const fenced = " |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/editor/Editor.tsx` around lines 1232 - 1247, When pasteMode
=== "code-block", ensure a deterministic fallback inserts a codeBlock and
returns true even if manager is missing or manager.parse throws; if parsing
succeeds keep existing behavior (currentEditor.commands.insertContent(parsed));
otherwise normalize the pasted text (use a variable e.g. normalized =
text.replace(/\r\n?/g, '\n') or similar), then call
currentEditor.chain().focus().insertContent({ type: "codeBlock", attrs: {
language: null }, content: normalized ? [{ type: "text", text: normalized }] :
[] }).run(); finally return true so paste handling stays in code-block mode
instead of falling back to default.
| const setPasteMode = useCallback(async (mode: PasteMode) => { | ||
| setPasteModeState(mode); | ||
| try { | ||
| const settings = await getSettings(); | ||
| await updateSettings({ ...settings, pasteMode: mode }); | ||
| } catch (error) { |
There was a problem hiding this comment.
Avoid read-modify-write overwrites when persisting pasteMode.
This setter reads the full settings object and writes it back with one field changed. Concurrent setting updates can clobber each other and drop unrelated fields. Prefer an atomic backend patch command (e.g., update only pasteMode) or a versioned/serialized write strategy.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/context/ThemeContext.tsx` around lines 558 - 563, The setPasteMode setter
currently does a read-modify-write by calling getSettings() then
updateSettings({...settings, pasteMode: mode}), which can clobber concurrent
changes; change setPasteMode (and related callers) to persist only the changed
field atomically by calling an API that accepts a partial update (e.g.,
updateSettings({ pasteMode: mode }) or a new updateSettingField('pasteMode',
mode)) instead of spreading the full settings object, or implement a
versioned/conditional write in updateSettings to prevent lost updates; keep
setPasteModeState(mode) locally, await the atomic patch/field-update call (or
handle version conflicts) and preserve the existing error handling in the catch
block.
Summary
.scratch/settings.json:```code blockChanges
src/types/note.ts— addsPasteModetype andpasteModefield toSettingssrc/context/ThemeContext.tsx— loads, stores, and exposespasteMode/setPasteModesrc/components/editor/Editor.tsx—handlePastebranches onpasteModesrc/components/settings/EditorSettingsSection.tsx— adds "Editing" section with a Select for paste modeTest plan
🤖 Generated with Claude Code
Summary by CodeRabbit