@@ -18,6 +18,7 @@ import { EditorView, keymap } from "@codemirror/view";
1818import type { Note } from "../types" ;
1919import { useCodeMirror } from "../hooks/useCodeMirror" ;
2020import { useNoteAutosave } from "../hooks/useNoteAutosave" ;
21+ import { useClipboardImagePaste } from "../hooks/useClipboardImagePaste" ;
2122import { headingCommand , headingKeymap } from "../commands/headingCommand" ;
2223import { insertTextCommand , wrapSelectionCommand } from "../commands/toolbarCommands" ;
2324import { imageDecorationPlugin } from "./imageDecorationPlugin" ;
@@ -66,6 +67,26 @@ const RichEditorView = forwardRef<NoteEditHandle, Props>(function RichEditorView
6667 useEffect ( ( ) => { onCancelRef . current = onCancel ; } , [ onCancel ] ) ;
6768 useEffect ( ( ) => { onImageOpenRef . current = onImageOpen ; } , [ onImageOpen ] ) ;
6869
70+ // ── v0.15 剪贴板图片:粘贴即落盘(import_note_image_b64)+ 插入相对引用 ──
71+ const onInsertImage = useCallback ( ( rel : string ) => {
72+ const view = viewRef . current ;
73+ if ( ! view ) return ;
74+ const main = view . state . selection . main ;
75+ const line = view . state . doc . lineAt ( main . head ) ;
76+ // 空行粘贴:带尾换行(独立行图片 block 化,不留行内空隙);行中粘贴:纯内联
77+ const text = line . text . trim ( ) === "" ? `\n` : `` ;
78+ view . dispatch ( {
79+ changes : { from : main . from , to : main . to , insert : text } ,
80+ selection : { anchor : main . from + text . length } ,
81+ userEvent : "input.paste" ,
82+ } ) ;
83+ } , [ ] ) ;
84+ const handleImagePaste = useClipboardImagePaste ( {
85+ noteId : note . id ,
86+ onInsert : onInsertImage ,
87+ onError : ( msg ) => setStatus ( msg ) ,
88+ } ) ;
89+
6990 // ── 保存 + 草稿层(双计时器/flushLatest/卸载保存/草稿节流,spec §4.4)──
7091 const getSnapshot = useCallback ( ( ) => ( { title : titleRef . current , content : contentRef . current } ) , [ ] ) ;
7192 const autosave = useNoteAutosave ( {
@@ -102,9 +123,11 @@ const RichEditorView = forwardRef<NoteEditHandle, Props>(function RichEditorView
102123 { key : "Mod-s" , run : ( ) => { autosave . saveVersioned ( ) ; return true ; } } ,
103124 { key : "Mod-e" , run : ( ) => { void autosave . flushLatestRef . current ( ) . finally ( ( ) => onCancelRef . current ( ) ) ; return true ; } } ,
104125 ] ) ,
126+ // v0.15:粘贴图片 → 拦截 + 落盘 + 插入引用(文字粘贴走默认行为)
127+ EditorView . domEventHandlers ( { paste : ( e ) => handleImagePaste ( e ) } ) ,
105128 imageDecorationPlugin ( { noteId : note . id , onOpen : ( url , t ) => onImageOpenRef . current ?.( url , t ) } ) ,
106129 editorTheme ,
107- ] , [ note . id , autosave . saveVersioned , autosave . flushLatestRef ] ) ;
130+ ] , [ note . id , autosave . saveVersioned , autosave . flushLatestRef , handleImagePaste ] ) ;
108131
109132 const { containerRef, viewRef } = useCodeMirror ( {
110133 doc : content ,
@@ -141,9 +164,17 @@ const RichEditorView = forwardRef<NoteEditHandle, Props>(function RichEditorView
141164 }
142165 case "latex" : wrapSelectionCommand ( "$" , "$" ) ( view ) ; break ;
143166 case "image" : {
167+ // v0.15:外链图自动下载复制(防源站删除丢资源)——失败降级插原 URL + 提示
144168 const alt = prompt ( "图片描述:" ) || "" ;
145169 const url = prompt ( "图片链接:" , "https://" ) ;
146- if ( url ) insertTextCommand ( `\n` ) ( view ) ;
170+ if ( ! url ) break ;
171+ try {
172+ const rel = await invoke < string > ( "import_note_image_url" , { noteId : note . id , url } ) ;
173+ insertTextCommand ( `\n` ) ( view ) ;
174+ } catch ( err ) {
175+ insertTextCommand ( `\n` ) ( view ) ;
176+ setStatus ( `外链图下载失败(已插入原链接——可能随源站点删除而失效): ${ err } ` ) ;
177+ }
147178 break ;
148179 }
149180 case "local-image" : {
@@ -214,7 +245,7 @@ const RichEditorView = forwardRef<NoteEditHandle, Props>(function RichEditorView
214245 < button style = { TOOLBAR_BTN } onClick = { ( ) => void toolbarAction ( "table" ) } title = "表格" > ⊞ 表格</ button >
215246 < button style = { TOOLBAR_BTN } onClick = { ( ) => void toolbarAction ( "link" ) } title = "链接" > 🔗</ button >
216247 < button style = { TOOLBAR_BTN } onClick = { ( ) => void toolbarAction ( "local-image" ) } title = "插入本地图片(复制进应用数据目录)" > 🖼 图片</ button >
217- < button style = { TOOLBAR_BTN } onClick = { ( ) => void toolbarAction ( "image" ) } title = "插入外部链接图 " > 🌐 链接图</ button >
248+ < button style = { TOOLBAR_BTN } onClick = { ( ) => void toolbarAction ( "image" ) } title = "插入外链图(自动下载为本地副本) " > 🌐 链接图</ button >
218249 < button style = { TOOLBAR_BTN } onClick = { ( ) => void toolbarAction ( "latex" ) } title = "LaTeX" > Σ</ button >
219250 < span style = { { flex : 1 } } />
220251 < span style = { { fontSize : 10 , color : "#9ca3af" } } > Ctrl+Shift+↑↓层级 · Ctrl+Z 撤销</ span >
0 commit comments