@@ -73,12 +73,22 @@ export function useEditor() {
7373 // ── content change ─────────────────────────────────────────────────────────
7474 const handleContentChange = useCallback (
7575 ( value : string | undefined ) => {
76- const v = value ?? "" ;
77- if ( v === activeTab . content ) return ;
78- updateTab ( activeTabId , { content : v , dirty : true } ) ;
79- refreshPreview ( v ) ;
76+ if ( typeof value !== "string" ) return ;
77+ const v = value ;
78+ let changed = false ;
79+
80+ setTabs ( ( prev ) =>
81+ prev . map ( ( t ) => {
82+ if ( t . id !== activeTabId ) return t ;
83+ if ( t . content === v ) return t ;
84+ changed = true ;
85+ return { ...t , content : v , dirty : true } ;
86+ } )
87+ ) ;
88+
89+ if ( changed ) refreshPreview ( v ) ;
8090 } ,
81- [ activeTab , activeTabId , updateTab , refreshPreview ]
91+ [ activeTabId , refreshPreview ]
8292 ) ;
8393
8494 // ── file ops ───────────────────────────────────────────────────────────────
@@ -129,27 +139,7 @@ export function useEditor() {
129139
130140 const saveFile = useCallback (
131141 async ( filePath ?: string ) => {
132- let path = filePath ?? activeTab . filePath ;
133-
134- // For tabs opened without absolute path, try to resolve an existing path
135- // without opening any dialog.
136- if ( ! path ) {
137- const byName = recentFiles . filter ( ( p ) => ( p . split ( / [ / \\ ] / ) . pop ( ) ?? "" ) === activeTab . label ) ;
138- if ( byName . length === 1 ) {
139- path = byName [ 0 ] ;
140- } else {
141- const candidates = [ activeTab . label , `./${ activeTab . label } ` ] ;
142- for ( const candidate of candidates ) {
143- try {
144- await Files . read ( candidate ) ;
145- path = candidate ;
146- break ;
147- } catch {
148- // Keep trying other candidates.
149- }
150- }
151- }
152- }
142+ const path = filePath ?? activeTab . filePath ;
153143
154144 if ( path ) {
155145 await Files . write ( path , activeTab . content ) ;
@@ -164,40 +154,10 @@ export function useEditor() {
164154 return ;
165155 }
166156
167- // Save As flow: ask user for a destination when the tab has no path yet.
168- const rawLabel = activeTab . label . trim ( ) || "Untitled" ;
169- const suggestedName = / \. [ A - Z a - z 0 - 9 ] + $ / . test ( rawLabel ) ? rawLabel : `${ rawLabel } .md` ;
170-
171- // Try Tauri native save dialog first (desktop mode).
172- try {
173- const { save } = await import ( "@tauri-apps/plugin-dialog" ) ;
174- const target = await save ( {
175- defaultPath : suggestedName ,
176- filters : [ { name : "Markdown" , extensions : [ "md" , "markdown" , "txt" ] } ] ,
177- } ) ;
178-
179- if ( ! target ) return ;
180-
181- const resolvedPath = Array . isArray ( target ) ? target [ 0 ] : target ;
182- await Files . write ( resolvedPath , activeTab . content ) ;
183- updateTab ( activeTabId , {
184- dirty : false ,
185- filePath : resolvedPath ,
186- browserHandle : null ,
187- label : resolvedPath . split ( / [ / \\ ] / ) . pop ( ) ?? resolvedPath ,
188- } ) ;
189- Files . addRecent ( resolvedPath )
190- . then ( ( { entries } ) => setRecentFiles ( entries ) )
191- . catch ( console . error ) ;
192- return ;
193- } catch {
194- // Not running with Tauri dialog capability.
195- }
196-
197- // Outside Tauri dialog capability, do nothing silently.
157+ // No explicit path available: do nothing silently.
198158 return ;
199159 } ,
200- [ activeTab , activeTabId , recentFiles , updateTab ]
160+ [ activeTab , activeTabId , updateTab ]
201161 ) ;
202162
203163 const newTab = useCallback ( ( ) => {
0 commit comments