diff --git a/src/main.ts b/src/main.ts index 2b4459f..a196c07 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,6 +12,7 @@ import { App, HeadingCache, + ListedFiles, MarkdownView, Modal, Notice, @@ -33,6 +34,7 @@ import { NameObj, path, sanitizer, + getDirectoryPath, } from './utils'; interface PluginSettings { @@ -157,9 +159,16 @@ export default class PasteImageRenamePlugin extends Plugin { const linkText = this.app.fileManager.generateMarkdownLink(file, sourcePath) // file system operation: rename the file - const newPath = path.join(file.parent.path, newName) + // const newPath = path.join(file.parent.path, newName) try { - await this.app.fileManager.renameFile(file, newPath) + // get directory part of new path + const newPathDirectory = path.directory(newName) + // check if directory exists + const newPathDirectoryExists = await this.app.vault.adapter.exists(newPathDirectory) + // create directory + if (!newPathDirectoryExists) await this.app.vault.createFolder(newPathDirectory) + // execute rename + await this.app.fileManager.renameFile(file, newName) } catch (err) { new Notice(`Failed to rename ${newName}: ${err}`) throw err @@ -276,12 +285,15 @@ export default class PasteImageRenamePlugin extends Plugin { console.warn('could not get file cache from active file', activeFile.name) } + const dirPath = getDirectoryPath(activeFile.parent) + const stem = renderTemplate( this.settings.imageNamePattern, { imageNameKey, fileName: activeFile.basename, dirName: activeFile.parent.name, + dirPath, firstHeading, }, frontmatter) @@ -296,14 +308,17 @@ export default class PasteImageRenamePlugin extends Plugin { // newName: foo.ext async deduplicateNewName(newName: string, file: TFile): Promise { + // confirmed new file path + const newFilePath = path.join(getDirectoryPath(file.parent), newName); // list files in dir - const dir = file.parent.path - const listed = await this.app.vault.adapter.list(dir) + const dir = path.directory(newFilePath) // file.parent.path + const dirExists = await this.app.vault.adapter.exists(dir) + const listed: false | ListedFiles = dirExists && await this.app.vault.adapter.list(dir) debugLog('sibling files', listed) // parse newName const newNameExt = path.extension(newName), - newNameStem = newName.slice(0, newName.length - newNameExt.length - 1), + newNameStem = newFilePath.slice(0, newFilePath.length - newNameExt.length - 1), newNameStemEscaped = escapeRegExp(newNameStem), delimiter = this.settings.dupNumberDelimiter, delimiterEscaped = escapeRegExp(delimiter) @@ -320,18 +335,20 @@ export default class PasteImageRenamePlugin extends Plugin { const dupNameNumbers: number[] = [] let isNewNameExist = false - for (let sibling of listed.files) { - sibling = path.basename(sibling) - if (sibling == newName) { - isNewNameExist = true - continue - } + if (listed) { + for (let sibling of listed.files) { + let siblingBasename = path.basename(sibling) + if (siblingBasename == newName) { + isNewNameExist = true + continue + } - // match dupNames - const m = dupNameRegex.exec(sibling) - if (!m) continue - // parse int for m.groups.number - dupNameNumbers.push(parseInt(m.groups.number)) + // match dupNames + const m = dupNameRegex.exec(sibling) + if (!m) continue + // parse int for m.groups.number + dupNameNumbers.push(parseInt(m.groups.number)) + } } if (isNewNameExist || this.settings.dupNumberAlways) { @@ -559,6 +576,7 @@ The pattern indicates how the new name should be generated. Available variables: - {{fileName}}: name of the active file, without ".md" extension. - {{dirName}}: name of the directory which contains the document (the root directory of vault results in an empty variable). +- {{dirPath}}: full path of the directory which contains the document - {{imageNameKey}}: this variable is read from the markdown file's frontmatter, from the same key "imageNameKey". - {{DATE:$FORMAT}}: use "$FORMAT" to format the current date, "$FORMAT" must be a Moment.js format string, e.g. {{DATE:YYYY-MM-DD}}. diff --git a/src/template.ts b/src/template.ts index 3976780..b1f633d 100644 --- a/src/template.ts +++ b/src/template.ts @@ -20,6 +20,7 @@ interface TemplateData { imageNameKey: string fileName: string dirName: string + dirPath: string firstHeading: string } @@ -38,6 +39,7 @@ export const renderTemplate = (tmpl: string, data: TemplateData, frontmatter?: F .replace(/{{imageNameKey}}/gm, data.imageNameKey) .replace(/{{fileName}}/gm, data.fileName) .replace(/{{dirName}}/gm, data.dirName) + .replace(/{{dirPath}}/gm, data.dirPath) .replace(/{{firstHeading}}/gm, data.firstHeading) return text } diff --git a/src/utils.ts b/src/utils.ts index 50c22b1..fefb1ee 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,7 @@ import { App, Vault, + TFolder, } from 'obsidian'; export const DEBUG = !(process.env.BUILD_ENV === 'production') @@ -64,6 +65,17 @@ export const path = { return sp[sp.length - 1] }, + /** + * get the parent directory part of a file or directory + * @param fullpath - full path of a file or directory + * @returns the directory part of a path, + * @example + */ + directory(fullpath: string): string { + const sp = fullpath.split('/') + return sp.slice(0, sp.length - 1).join('/') + }, + // return extension without dot, e.g. 'jpg' extension(fullpath: string): string { const positions = [...fullpath.matchAll(new RegExp('\\.', 'gi'))].map(a => a.index) @@ -71,6 +83,16 @@ export const path = { }, } +/** + * get the full path of given folder object + * @param tFolder - a folder object + * @returns the full path of directory + */ +export const getDirectoryPath = (tFolder: TFolder): string => { + if (tFolder.parent.name === '') return tFolder.name + return `${getDirectoryPath(tFolder.parent)}/${tFolder.name}` +} + const filenameNotAllowedChars = /[^\p{L}0-9~`!@$&*()\-_=+{};'",<.>? ]/ug export const sanitizer = {