@@ -36,6 +36,26 @@ function striptHtmlComments(s: string): string {
3636 return s . replace ( / < ! - - [ \s \S ] * ?- - > / g, "" ) ;
3737}
3838
39+ function ensureValidFilenameWithExtension ( filename : string ) : string {
40+ if ( isValidPath ( filename ) ) {
41+ return filename ;
42+ }
43+ const match = filename . match ( / \. ( [ ^ . ] + ) $ / ) ;
44+ return `file.${ match ? match [ 1 ] : "txt" } ` ;
45+ }
46+
47+ async function doesFileExist (
48+ editor : Client ,
49+ filePath : string ,
50+ ) : Promise < boolean > {
51+ try {
52+ await editor . space . spacePrimitives . getFileMeta ( filePath ) ;
53+ return true ;
54+ } catch {
55+ return false ;
56+ }
57+ }
58+
3959const urlRegexp =
4060 / ^ h t t p s ? : \/ \/ [ - a - z A - Z 0 - 9 @ : % . _ \+ ~ # = ] { 1 , 256 } ( [ - a - z A - Z 0 - 9 ( ) @ : % _ \+ . ~ # ? & / / = ] * ) / ;
4161
@@ -207,6 +227,9 @@ export function documentExtension(editor: Client) {
207227
208228 async function saveFile ( file : UploadFile ) {
209229 const maxSize = maximumDocumentSize ;
230+ const invalidPathMessage =
231+ "Unable to upload file, invalid target filename or path" ;
232+
210233 if ( file . content . length > maxSize * 1024 * 1024 ) {
211234 editor . flashNotification (
212235 `Document is too large, maximum is ${ maxSize } MiB` ,
@@ -215,21 +238,62 @@ export function documentExtension(editor: Client) {
215238 return ;
216239 }
217240
218- const finalFilePath = await editor . prompt (
241+ let desiredFilePath = await editor . prompt (
219242 "File name for pasted document" ,
220243 resolveMarkdownLink (
221244 client . currentPath ( ) ,
222- isValidPath ( file . name )
223- ? file . name
224- : `file.${
225- file . name . indexOf ( "." ) !== - 1 ? file . name . split ( "." ) . pop ( ) : "txt"
226- } `,
245+ ensureValidFilenameWithExtension ( file . name ) ,
227246 ) ,
228247 ) ;
229- if ( ! finalFilePath || ! isValidName ( finalFilePath ) ) {
248+ if ( desiredFilePath === undefined ) {
249+ // User hit cancel, so they know why we stopped and dont need an notification.
250+ return ;
251+ }
252+ desiredFilePath = desiredFilePath . trim ( ) ;
253+ if ( ! isValidName ( desiredFilePath ) ) {
254+ editor . flashNotification ( invalidPathMessage , "error" ) ;
230255 return ;
231256 }
232257
258+ // Check the given desired file path wont clobber an existing file. If it
259+ // would, ask the user to confirm or provide another filename. Repeat this
260+ // check for every new filename they give.
261+ // Note: duplicate any modifications here to client/code_mirror/editor_paste.ts
262+ let finalFilePath = null ;
263+ while ( finalFilePath == null ) {
264+ if ( await doesFileExist ( editor , desiredFilePath ) ) {
265+ let confirmedFilePath = await editor . prompt (
266+ "A file with that name already exists, keep the same name to replace it, or rename your file" ,
267+ resolveMarkdownLink (
268+ client . currentPath ( ) ,
269+ ensureValidFilenameWithExtension ( desiredFilePath ) ,
270+ ) ,
271+ ) ;
272+ if ( confirmedFilePath === undefined ) {
273+ // Unlike the initial filename prompt, we're inside a workflow here
274+ // and should be explicit that the user action cancelled the whole
275+ // operation.
276+ editor . flashNotification ( "Upload cancelled by user" , "info" ) ;
277+ return ;
278+ }
279+ confirmedFilePath = confirmedFilePath . trim ( ) ;
280+ if ( ! isValidPath ( confirmedFilePath ) ) {
281+ editor . flashNotification ( invalidPathMessage , "error" ) ;
282+ return ;
283+ }
284+ if ( desiredFilePath === confirmedFilePath ) {
285+ // if we got back the same path, we're replacing and should accept the given name
286+ finalFilePath = desiredFilePath ;
287+ } else {
288+ // we got a new path, so we must repeat the check
289+ desiredFilePath = confirmedFilePath ;
290+ confirmedFilePath = undefined ;
291+ }
292+ } else {
293+ finalFilePath = desiredFilePath ;
294+ }
295+ }
296+
233297 await editor . space . writeDocument ( finalFilePath , file . content ) ;
234298 let documentMarkdown = `[[${ finalFilePath } ]]` ;
235299 if ( file . contentType . startsWith ( "image/" ) ) {
0 commit comments