diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d800e64..4ffe2f2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -27,3 +27,4 @@ jobs: run: | npm install npm run build + npm run test diff --git a/README.md b/README.md index d7006ad..cc0b8e6 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,39 @@ Now you can rename a bunch of files from the directory and all references also w > Under the hood this plugin is using obsidian API for renaming, but we just applying it for many files -## How to use? +# Features +> Whenever we're updating **Replacement Symbols** you can set new _Directory Location_ too +> so, you can also move files to _another directory_ + + +## Rename/Move files based on folder location +Click _Search By Folder_ + +Update **Folder Location** where are you wanting to get files from, put **Existing Characters** from the file path +later on update **Replacement Symbols** those symbols will be used for in a new path. + + +## Rename/Move files based on tags +Click _Search By Tags_ + +Put tags in **Tags names** field search will be completed across the vault, use coma separator if you need more than 1 tag +Click Refresh +Update **Existing Characters** field with a pattern you are looking for in existing notes, set **Replacement Symbols** + +--- + +Click Preview or `Enter` to see intermediate results(nothing will be changed/moved/renamed). + +Click `Rename` whenever you done + + + +## Update files based on tags + + + +## API - **folder location** - Files from which folder you need to rename - **Symbols in existing files** - the symbols/characters that we have in the files - **Replacement Symbols** - a new symbols that will be pasted instead diff --git a/main.ts b/main.ts index 2ecb6fb..1698a04 100644 --- a/main.ts +++ b/main.ts @@ -2,12 +2,7 @@ import { App, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian'; import { FolderSuggest } from './src/suggestions/folderSuggest'; import { renderDonateButton } from './src/components/DonateButton'; -import { - getFilesNamesInDirectory, - getRenderedFileNamesReplaced, - renameFilesInObsidian, - syncScrolls, -} from './src/services/file.service'; +import { renameFilesInObsidian } from './src/services/file.service'; import { createPreviewElement } from './src/components/PreviewElement'; import { getObsidianFilesByFolderName, @@ -83,6 +78,13 @@ export class BulkRenameSettingsTab extends PluginSettingTab { const { containerEl } = this; containerEl.empty(); containerEl.createEl('h2', { text: 'Bulk Rename - Settings' }); + containerEl.addEventListener('keyup', (event) => { + if (event.key !== 'Enter') { + return; + } + + this.reRenderPreview(); + }); this.renderTabs(); this.renderFileLocation(); @@ -95,7 +97,7 @@ export class BulkRenameSettingsTab extends PluginSettingTab { renderTabs() { new Setting(this.containerEl) - .setName('toggle view') + .setName('UI will be changed when you click those buttons') .addButton((button) => { button.setButtonText('Search by folder'); if (isViewTypeFolder(this.plugin)) { @@ -134,17 +136,14 @@ export class BulkRenameSettingsTab extends PluginSettingTab { .onChange((newFolder) => { this.plugin.settings.folderName = newFolder; this.plugin.saveSettings(); - this.calculateFiles(); + this.getFilesByFolder(); }); // @ts-ignore cb.containerEl.addClass('templater_search'); }) .addButton((button) => { button.setButtonText('Refresh'); - button.onClick(() => { - this.calculateFiles(); - this.reRenderPreview(); - }); + button.onClick(this.reRenderPreview); }); } @@ -165,8 +164,6 @@ export class BulkRenameSettingsTab extends PluginSettingTab { this.plugin.settings.tags = target.value.replace(/ /g, '').split(','); this.plugin.saveSettings(); - this.getFilesByTags(); - this.reRenderPreview(); }); cb.setPlaceholder('Example: #tag, #tag2') .setValue(this.plugin.settings.tags.join(',')) @@ -180,10 +177,7 @@ export class BulkRenameSettingsTab extends PluginSettingTab { }) .addButton((button) => { button.setButtonText('Refresh'); - button.onClick(() => { - this.getFilesByTags(); - this.reRenderPreview(); - }); + button.onClick(this.reRenderPreview); }); } @@ -202,10 +196,9 @@ export class BulkRenameSettingsTab extends PluginSettingTab { desc.appendChild(button); const newSettings = new Setting(this.containerEl) - .setName('Existing Symbol') + .setName('Existing Characters') .setDesc(desc); - // if (!isViewTypeTags(this.plugin)) { newSettings.addText((textComponent) => { textComponent.setValue(settings.existingSymbol); textComponent.setPlaceholder('existing symbols'); @@ -214,7 +207,7 @@ export class BulkRenameSettingsTab extends PluginSettingTab { this.plugin.saveSettings(); }); }); - // } + newSettings.addText((textComponent) => { const previewLabel = createPreviewElement('Replacement symbols'); textComponent.inputEl.insertAdjacentElement('beforebegin', previewLabel); @@ -223,7 +216,7 @@ export class BulkRenameSettingsTab extends PluginSettingTab { textComponent.onChange((newValue) => { settings.replacePattern = newValue; this.plugin.saveSettings(); - this.calculateFiles(); + this.getFilesByFolder(); }); }); } @@ -232,6 +225,7 @@ export class BulkRenameSettingsTab extends PluginSettingTab { this.filesAndPreview = new Setting(this.containerEl) .setName('Files within the folder') .setDesc(`Total Files: ${this.plugin.settings.fileNames.length}`); + this.calculateFileNames(); renderPreviewFiles(this.filesAndPreview, this.plugin, this.state); }; @@ -268,7 +262,20 @@ export class BulkRenameSettingsTab extends PluginSettingTab { renderDonateButton(this.containerEl); } - calculateFiles() { + reRenderPreview = () => { + this.calculateFileNames(); + renderPreviewFiles(this.filesAndPreview, this.plugin, this.state); + }; + + calculateFileNames() { + if (isViewTypeTags(this.plugin)) { + this.getFilesByTags(); + return; + } + this.getFilesByFolder(); + } + + getFilesByFolder() { this.plugin.settings.fileNames = getObsidianFilesByFolderName( this.app, this.plugin, @@ -281,10 +288,6 @@ export class BulkRenameSettingsTab extends PluginSettingTab { this.plugin, ); } - - reRenderPreview = () => { - renderPreviewFiles(this.filesAndPreview, this.plugin, this.state); - }; } export default BulkRenamePlugin; diff --git a/src/components/RenderPreviewFiles.ts b/src/components/RenderPreviewFiles.ts index e017f0f..0d31151 100644 --- a/src/components/RenderPreviewFiles.ts +++ b/src/components/RenderPreviewFiles.ts @@ -1,10 +1,9 @@ import { getFilesNamesInDirectory, getRenderedFileNamesReplaced, - syncScrolls, } from '../services/file.service'; import { createPreviewElement } from './PreviewElement'; -import BulkRenamePlugin, { BulkRenameSettingsTab } from '../../main'; +import BulkRenamePlugin, { BulkRenameSettingsTab, State } from '../../main'; export const renderPreviewFiles = ( setting: BulkRenameSettingsTab['filesAndPreview'], @@ -43,3 +42,24 @@ export const renderPreviewFiles = ( syncScrolls(existingFilesTextArea, replacedPreviewTextArea, state); }); }; + +export const syncScrolls = ( + existingFilesArea: HTMLTextAreaElement, + previewArea: HTMLTextAreaElement, + state: State, +) => { + existingFilesArea.addEventListener('scroll', (event) => { + const target = event.target as HTMLTextAreaElement; + if (target.scrollTop !== state.previewScroll) { + previewArea.scrollTop = target.scrollTop; + state.previewScroll = target.scrollTop; + } + }); + previewArea.addEventListener('scroll', (event) => { + const target = event.target as HTMLTextAreaElement; + if (target.scrollTop !== state.filesScroll) { + existingFilesArea.scrollTop = target.scrollTop; + state.filesScroll = target.scrollTop; + } + }); +}; diff --git a/src/services/file.service.ts b/src/services/file.service.ts index 5c49be3..400e4ec 100644 --- a/src/services/file.service.ts +++ b/src/services/file.service.ts @@ -1,5 +1,5 @@ import { App, Notice, TFile } from 'obsidian'; -import BulkRenamePlugin, { State } from '../../main'; +import BulkRenamePlugin from '../../main'; export const getFilesNamesInDirectory = (plugin: BulkRenamePlugin) => { const { fileNames } = plugin.settings; @@ -26,7 +26,7 @@ export const getRenderedFileNamesReplaced = (plugin: BulkRenamePlugin) => { return getFilesAsString(newFiles); }; -const selectFilenamesWithReplacedPath = (plugin: BulkRenamePlugin) => { +export const selectFilenamesWithReplacedPath = (plugin: BulkRenamePlugin) => { const { fileNames } = plugin.settings; return fileNames.map((file) => { @@ -42,10 +42,11 @@ export const replaceFilePath = (plugin: BulkRenamePlugin, file: TFile) => { const pathWithoutExtension = file.path.split('.').slice(0, -1).join('.'); - const newPath = pathWithoutExtension?.replaceAll( - existingSymbol, - replacePattern, - ); + const convertedToRegExpString = escapeRegExp(existingSymbol); + + const regExpSymbol = new RegExp(convertedToRegExpString, 'g'); + + const newPath = pathWithoutExtension?.replace(regExpSymbol, replacePattern); return `${newPath}.${file.extension}`; }; @@ -61,11 +62,6 @@ export const renameFilesInObsidian = async ( return; } - // if (replacePattern === existingSymbol) { - // new Notice("Replace Pattern shouldn't much Existing Symbol"); - // return; - // } - if (!fileNames.length) { new Notice('Please check your results before rename!'); return; @@ -81,23 +77,9 @@ export const renameFilesInObsidian = async ( new Notice('successfully renamed all files'); }; -export const syncScrolls = ( - existingFilesArea: HTMLTextAreaElement, - previewArea: HTMLTextAreaElement, - state: State, -) => { - existingFilesArea.addEventListener('scroll', (event) => { - const target = event.target as HTMLTextAreaElement; - if (target.scrollTop !== state.previewScroll) { - previewArea.scrollTop = target.scrollTop; - state.previewScroll = target.scrollTop; - } - }); - previewArea.addEventListener('scroll', (event) => { - const target = event.target as HTMLTextAreaElement; - if (target.scrollTop !== state.filesScroll) { - existingFilesArea.scrollTop = target.scrollTop; - state.filesScroll = target.scrollTop; - } - }); -}; +let reRegExpChar = /[\\^$.*+?()[\]{}]/g, + reHasRegExpChar = RegExp(reRegExpChar.source); + +export function escapeRegExp(s: string) { + return s && reHasRegExpChar.test(s) ? s.replace(reRegExpChar, '\\$&') : s; +} diff --git a/src/services/file.services.test.ts b/src/services/file.services.test.ts index fac9ddf..c807d41 100644 --- a/src/services/file.services.test.ts +++ b/src/services/file.services.test.ts @@ -1,6 +1,9 @@ import { TFile } from 'obsidian'; -import { replaceFilePath } from './file.service'; +import { + replaceFilePath, + selectFilenamesWithReplacedPath, +} from './file.service'; import BulkRenamePlugin from '../../main'; describe('File Services', () => { @@ -79,5 +82,66 @@ describe('File Services', () => { expect(result).toEqual(expectedResult); }); + + describe('selectFilenamesWithReplacedPath', () => { + const files = [ + { + path: 'journals/2022_10_13.md', + extension: 'md', + }, + { + path: 'pages/2022_10_13.md', + extension: 'md', + }, + { + path: 'bulkRenameTets/2022_10_13.md', + extension: 'md', + }, + { + path: 'YesWecan/canWe/2022_10_13.md', + extension: 'md', + }, + ] as unknown as TFile[]; + + const mockPluginPlugin = { + settings: { + fileNames: files, + }, + } as unknown as BulkRenamePlugin; + + it('should rename many files with RegExp', () => { + const plugin = { + ...mockPluginPlugin, + settings: { + ...mockPluginPlugin.settings, + existingSymbol: 'journals|pages|bulkRenameTets|canWe', + replacePattern: 'qwe', + }, + } as unknown as BulkRenamePlugin; + + const expectedResults = [ + { + path: 'qwe/2022_10_13.md', + extension: 'md', + }, + { + path: 'qwe/2022_10_13.md', + extension: 'md', + }, + { + path: 'qwe/2022_10_13.md', + extension: 'md', + }, + { + path: 'YesWecan/qwe/2022_10_13.md', + extension: 'md', + }, + ]; + + const updatedFiles = selectFilenamesWithReplacedPath(plugin); + + expect(expectedResults).toEqual(updatedFiles); + }); + }); }); }); diff --git a/src/services/obsidian.service.test.ts b/src/services/obsidian.service.test.ts new file mode 100644 index 0000000..f810b56 --- /dev/null +++ b/src/services/obsidian.service.test.ts @@ -0,0 +1,5 @@ +describe('obsidian.service', () => { + describe('getObsidianFilesWithTagName', () => { + it.todo('should find files by tag'); + }); +}); diff --git a/src/services/obsidian.service.ts b/src/services/obsidian.service.ts index d83540b..c383da2 100644 --- a/src/services/obsidian.service.ts +++ b/src/services/obsidian.service.ts @@ -8,11 +8,11 @@ export const getObsidianFilesByFolderName = ( const { folderName } = plugin.settings; const abstractFiles = app.vault.getAllLoadedFiles(); - const files = abstractFiles.filter((file) => { - return file instanceof TFile && file.parent.name.includes(folderName); - }); + const files = abstractFiles.filter( + (file) => file instanceof TFile && file.parent.name.includes(folderName), + ) as TFile[]; - const filesSortedByName = files.sort((a, b) => a.name.localeCompare(b.name)); + const filesSortedByName = sortFilesByName(files); return filesSortedByName; }; @@ -28,12 +28,9 @@ export const getObsidianFilesWithTagName = ( if (!(file instanceof TFile)) { return; } - const fileMetadata = app.metadataCache.getFileCache(file); - if (!fileMetadata) { - return; - } - if (!fileMetadata.tags) { + const fileMetadata = app.metadataCache.getFileCache(file); + if (!fileMetadata || !fileMetadata.tags) { return; } @@ -46,9 +43,13 @@ export const getObsidianFilesWithTagName = ( } return file; - }); + }) as TFile[]; - const filesSortedByName = files.sort((a, b) => a.name.localeCompare(b.name)); + const filesSortedByName = sortFilesByName(files); return filesSortedByName; }; + +const sortFilesByName = (files: TFile[]) => { + return files.sort((a, b) => a.name.localeCompare(b.name)); +};