@@ -47,6 +47,13 @@ type FolderSelection = {
4747 isEmpty : boolean ;
4848} ;
4949
50+ class InvalidFolderPathError extends Error {
51+ constructor ( message : string ) {
52+ super ( message ) ;
53+ this . name = "InvalidFolderPathError" ;
54+ }
55+ }
56+
5057function isMacroAbortError ( error : unknown ) : error is MacroAbortError {
5158 return (
5259 error instanceof MacroAbortError ||
@@ -217,15 +224,17 @@ export abstract class TemplateEngine extends QuickAddEngine {
217224 }
218225
219226 try {
220- await this . ensureFolderExists ( selection ) ;
227+ this . validateFolderPath ( selection . resolved ) ;
221228 } catch ( error ) {
222- if ( this . isInvalidPathError ( error ) ) {
223- this . showInvalidFolderNotice ( error ) ;
229+ if ( error instanceof InvalidFolderPathError ) {
230+ new Notice ( error . message ) ;
224231 continue ;
225232 }
226233 throw error ;
227234 }
228235
236+ await this . ensureFolderExists ( selection ) ;
237+
229238 return selection ;
230239 }
231240 }
@@ -247,48 +256,48 @@ export abstract class TemplateEngine extends QuickAddEngine {
247256 return "" ;
248257 }
249258
250- try {
251- await this . ensureFolderExists ( selection ) ;
252- } catch ( error ) {
253- if ( this . isInvalidPathError ( error ) ) {
254- this . showInvalidFolderNotice ( error ) ;
255- return "" ;
259+ if ( selection . resolved ) {
260+ try {
261+ this . validateFolderPath ( selection . resolved ) ;
262+ } catch ( error ) {
263+ if ( error instanceof InvalidFolderPathError ) {
264+ new Notice ( error . message ) ;
265+ return "" ;
266+ }
267+ throw error ;
256268 }
257- throw error ;
258269 }
270+
271+ await this . ensureFolderExists ( selection ) ;
259272 return selection . resolved ;
260273 }
261274
262275 private normalizeFolderPath ( path : string ) : string {
263276 return path . trim ( ) . replace ( / ^ \/ + / , "" ) . replace ( / \/ + $ / , "" ) ;
264277 }
265278
266- private isInvalidPathError ( error : unknown ) : error is Error {
267- if ( ! ( error instanceof Error ) ) return false ;
268- return (
269- error . message . includes ( "File name cannot contain" ) ||
270- error . message . includes ( "File name cannot be empty" ) ||
271- error . message . includes ( "File name cannot start with" )
272- ) ;
273- }
279+ private validateFolderPath ( path : string ) : void {
280+ const trimmed = path . trim ( ) ;
281+ if ( ! trimmed ) return ;
274282
275- private showInvalidFolderNotice ( error : Error ) : void {
276- new Notice ( this . formatInvalidFolderMessage ( error . message ) ) ;
277- }
283+ const segments = trimmed . split ( "/" ) ;
284+ for ( const segment of segments ) {
285+ if ( ! segment ) {
286+ throw new InvalidFolderPathError ( "Folder name cannot be empty." ) ;
287+ }
278288
279- private formatInvalidFolderMessage ( message : string ) : string {
280- const invalidCharsMatch = message . match (
281- / ^ F i l e n a m e c a n n o t c o n t a i n a n y o f t h e f o l l o w i n g c h a r a c t e r s : \s * ( .+ ) $ / u,
282- ) ;
283- if ( invalidCharsMatch ?. [ 1 ] ) {
284- return `Folder name cannot contain any of the following characters: ${ invalidCharsMatch [ 1 ] } ` ;
285- }
289+ if ( segment === "." || segment === ".." ) {
290+ throw new InvalidFolderPathError (
291+ "Folder name cannot be '.' or '..'." ,
292+ ) ;
293+ }
286294
287- if ( message . startsWith ( "File name " ) ) {
288- return `Folder name ${ message . slice ( "File name " . length ) } ` ;
295+ if ( / [ \\ : ] / u. test ( segment ) ) {
296+ throw new InvalidFolderPathError (
297+ "Folder name cannot contain any of the following characters: \\ / :" ,
298+ ) ;
299+ }
289300 }
290-
291- return "Invalid folder name." ;
292301 }
293302
294303 private isPathAllowed ( path : string , roots : string [ ] ) : boolean {
0 commit comments