add find-by tag and to a directory functionality

This commit is contained in:
Oleg 2022-09-05 13:52:48 +03:00
parent 691e8855f5
commit 863e430134
8 changed files with 308 additions and 3760 deletions

View File

@ -11,8 +11,21 @@ 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 > Under the hood this plugin is using obsidian API for renaming, but we just applying it for many files
## How to use? # Features
## Update FilePath name based on pattern
Whenever we're updating **File Path** we can update _Directory Location_ too
So, you can not only update file name but also move files to a new directory
### Default showcase
Update **folder location** where from you want to get files, put **existing characters** from the file path
later on update **Replacement Symbols** those symbols will be used for in a new path.
## Update files based on tags
## API
- **folder location** - Files from which folder you need to rename - **folder location** - Files from which folder you need to rename
- **Symbols in existing files** - the symbols/characters that we have in the files - **Symbols in existing files** - the symbols/characters that we have in the files
- **Replacement Symbols** - a new symbols that will be pasted instead - **Replacement Symbols** - a new symbols that will be pasted instead
@ -36,3 +49,7 @@ And rename a **bunch of files** and update their reference in code base respecti
## Installing ## Installing
The plugin in beta. To beta test, you can install the plugin using BRAT (see [BRAT > Adding a beta plugin](https://github.com/TfTHacker/obsidian42-brat#adding-a-beta-plugin) for further instructions). The plugin in beta. To beta test, you can install the plugin using BRAT (see [BRAT > Adding a beta plugin](https://github.com/TfTHacker/obsidian42-brat#adding-a-beta-plugin) for further instructions).
## Support development
[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/oleglustenko)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

246
main.ts
View File

@ -1,20 +1,28 @@
import { App, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian'; import {
App,
Plugin,
PluginSettingTab,
Setting,
TAbstractFile,
} from 'obsidian';
import { FolderSuggest } from './src/suggestions/folderSuggest'; import { FolderSuggest } from './src/suggestions/folderSuggest';
import { renderDonateButton } from './src/components/DonateButton'; import { renderDonateButton } from './src/components/DonateButton';
import { import { renameFilesInObsidian } from './src/services/file.service';
getFilesNamesInDirectory,
getObsidianFiles,
getRenderedFileNamesReplaced,
renameFilesInObsidian,
syncScrolls,
} from './src/services/file.service';
import { createPreviewElement } from './src/components/PreviewElement'; import { createPreviewElement } from './src/components/PreviewElement';
import {
getObsidianFilesByFolderName,
getObsidianFilesWithTagName,
} from './src/services/obsidian.service';
import { renderPreviewFiles } from './src/components/RenderPreviewFiles';
interface BulkRenamePluginSettings { interface BulkRenamePluginSettings {
folderName: string; folderName: string;
fileNames: TFile[]; fileNames: TAbstractFile[];
existingSymbol: string; existingSymbol: string;
replacePattern: string; replacePattern: string;
tags: string[];
viewType: 'tags' | 'folder';
} }
const DEFAULT_SETTINGS: BulkRenamePluginSettings = { const DEFAULT_SETTINGS: BulkRenamePluginSettings = {
@ -22,6 +30,15 @@ const DEFAULT_SETTINGS: BulkRenamePluginSettings = {
fileNames: [], fileNames: [],
existingSymbol: '', existingSymbol: '',
replacePattern: '', replacePattern: '',
tags: [],
viewType: 'folder',
};
const isViewTypeFolder = ({ settings }: BulkRenamePlugin) => {
return settings.viewType === 'folder';
};
const isViewTypeTags = ({ settings }: BulkRenamePlugin) => {
return settings.viewType === 'tags';
}; };
class BulkRenamePlugin extends Plugin { class BulkRenamePlugin extends Plugin {
@ -48,9 +65,10 @@ export type State = {
filesScroll: number; filesScroll: number;
}; };
class BulkRenameSettingsTab extends PluginSettingTab { export class BulkRenameSettingsTab extends PluginSettingTab {
plugin: BulkRenamePlugin; plugin: BulkRenamePlugin;
state: State; state: State;
filesAndPreview: Setting;
constructor(app: App, plugin: BulkRenamePlugin) { constructor(app: App, plugin: BulkRenamePlugin) {
super(app, plugin); super(app, plugin);
@ -66,52 +84,54 @@ class BulkRenameSettingsTab extends PluginSettingTab {
const { containerEl } = this; const { containerEl } = this;
containerEl.empty(); containerEl.empty();
containerEl.createEl('h2', { text: 'Bulk Rename - Settings' }); containerEl.createEl('h2', { text: 'Bulk Rename - Settings' });
containerEl.addEventListener('keyup', (event) => {
if (event.key !== 'Enter') {
return;
}
this.reRenderPreview();
});
this.renderTabs();
this.renderFileLocation(); this.renderFileLocation();
this.renderTagNames();
this.renderReplaceSymbol(); this.renderReplaceSymbol();
this.renderFilesAndPreview(); this.renderFilesAndPreview();
this.renderRenameFiles(); this.renderRenameFiles();
this.renderSupportDevelopment(); this.renderSupportDevelopment();
} }
renderReplaceSymbol() { renderTabs() {
const { settings } = this.plugin;
const desc = document.createDocumentFragment();
const button = document.createElement('button');
button.textContent = 'Preview';
button.className = 'mod-cta';
button.onclick = () => {
this.display();
};
desc.appendChild(button);
new Setting(this.containerEl) new Setting(this.containerEl)
.setName('Existing Symbol') .setName('UI will be changed when you click those buttons')
.setDesc(desc) .addButton((button) => {
.addText((textComponent) => { button.setButtonText('Search by folder');
const previewLabel = createPreviewElement('Replacement symbols'); if (isViewTypeFolder(this.plugin)) {
textComponent.inputEl.insertAdjacentElement('afterend', previewLabel); button.setCta();
textComponent.setValue(settings.existingSymbol); }
textComponent.setPlaceholder('existing symbols'); button.onClick(async () => {
textComponent.onChange((newValue) => { this.plugin.settings.viewType = 'folder';
settings.existingSymbol = newValue; await this.plugin.saveSettings();
this.plugin.saveSettings(); this.display();
}); });
}) })
.addText((textComponent) => { .addButton((button) => {
textComponent.setValue(settings.replacePattern); button.setButtonText('Search By Tags');
textComponent.setPlaceholder('replace with'); if (isViewTypeTags(this.plugin)) {
textComponent.onChange((newValue) => { button.setCta();
settings.replacePattern = newValue; }
this.plugin.saveSettings(); button.onClick(async () => {
this.calculateFiles(); this.plugin.settings.viewType = 'tags';
await this.plugin.saveSettings();
this.display();
}); });
}); });
} }
renderFileLocation() { renderFileLocation() {
if (!isViewTypeFolder(this.plugin)) {
return;
}
new Setting(this.containerEl) new Setting(this.containerEl)
.setName('Folder location') .setName('Folder location')
.setDesc('Files in this folder will be available renamed.') .setDesc('Files in this folder will be available renamed.')
@ -122,54 +142,101 @@ class BulkRenameSettingsTab extends PluginSettingTab {
.onChange((newFolder) => { .onChange((newFolder) => {
this.plugin.settings.folderName = newFolder; this.plugin.settings.folderName = newFolder;
this.plugin.saveSettings(); this.plugin.saveSettings();
this.calculateFiles(); this.getFilesByFolder();
}); });
// @ts-ignore // @ts-ignore
cb.containerEl.addClass('templater_search'); cb.containerEl.addClass('templater_search');
}) })
.addButton((button) => { .addButton((button) => {
button.setButtonText('Refresh'); button.setButtonText('Refresh');
button.onClick(() => { button.onClick(this.reRenderPreview);
this.calculateFiles();
this.display();
});
}); });
} }
renderFilesAndPreview() { renderTagNames() {
let existingFilesTextArea: HTMLTextAreaElement; if (!isViewTypeTags(this.plugin)) {
let replacedPreviewTextArea: HTMLTextAreaElement; return;
}
new Setting(this.containerEl) new Setting(this.containerEl)
.setName('Files within the folder') .setName('Tag names ')
.setDesc(`Total Files: ${this.plugin.settings.fileNames.length}`) .setDesc('all files with the tags will be found')
.addTextArea((text) => { .addSearch((cb) => {
text.setPlaceholder('Here you will see files under folder location'); // @ts-ignore
text.setDisabled(true); cb.inputEl.addEventListener('keydown', (event) => {
existingFilesTextArea = text.inputEl; if (event.key !== 'Enter') {
return;
}
const target = event.target as HTMLInputElement;
const value = getFilesNamesInDirectory(this.plugin); this.plugin.settings.tags = target.value.replace(/ /g, '').split(',');
text.setValue(value); this.plugin.saveSettings();
});
const previewLabel = createPreviewElement(); cb.setPlaceholder('Example: #tag, #tag2')
text.inputEl.insertAdjacentElement('afterend', previewLabel); .setValue(this.plugin.settings.tags.join(','))
text.inputEl.addClass('templater_cmd'); .onChange((newFolder) => {
this.plugin.settings.tags = newFolder.replace(/ /g, '').split(',');
this.plugin.saveSettings();
this.getFilesByTags();
});
// @ts-ignore
cb.containerEl.addClass('templater_search');
}) })
.addTextArea((text) => { .addButton((button) => {
text.setPlaceholder( button.setButtonText('Refresh');
'How filenames will looks like after replacement(click preview first)', button.onClick(this.reRenderPreview);
);
text.setDisabled(true);
replacedPreviewTextArea = text.inputEl;
const value = getRenderedFileNamesReplaced(this.plugin);
text.setValue(value);
text.inputEl.addClass('templater_cmd');
})
.then((setting) => {
syncScrolls(existingFilesTextArea, replacedPreviewTextArea, this.state);
}); });
} }
renderReplaceSymbol() {
// if (isViewTypeTags(this.plugin)) {
// return;
// }
const { settings } = this.plugin;
const desc = document.createDocumentFragment();
const button = document.createElement('button');
button.textContent = 'Preview';
button.className = 'mod-cta';
button.onclick = this.reRenderPreview;
desc.appendChild(button);
const newSettings = new Setting(this.containerEl)
.setName('Existing Characters')
.setDesc(desc);
// if (!isViewTypeTags(this.plugin)) {
newSettings.addText((textComponent) => {
textComponent.setValue(settings.existingSymbol);
textComponent.setPlaceholder('existing symbols');
textComponent.onChange((newValue) => {
settings.existingSymbol = newValue;
this.plugin.saveSettings();
});
});
// }
newSettings.addText((textComponent) => {
const previewLabel = createPreviewElement('Replacement symbols');
textComponent.inputEl.insertAdjacentElement('beforebegin', previewLabel);
textComponent.setValue(settings.replacePattern);
textComponent.setPlaceholder('replace with');
textComponent.onChange((newValue) => {
settings.replacePattern = newValue;
this.plugin.saveSettings();
this.getFilesByFolder();
});
});
}
renderFilesAndPreview = () => {
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);
};
renderRenameFiles() { renderRenameFiles() {
const desc = document.createDocumentFragment(); const desc = document.createDocumentFragment();
desc.append( desc.append(
@ -186,12 +253,14 @@ class BulkRenameSettingsTab extends PluginSettingTab {
.setName('Replace patterns') .setName('Replace patterns')
.addButton((button) => { .addButton((button) => {
button.buttonEl.style.width = '100%'; button.buttonEl.style.width = '100%';
button.setTooltip("FYI: We don't have undone button yet!"); button.setTooltip(
"We don't have undone button yet!\r\n Do we need it?",
);
button.setButtonText('Rename'); button.setButtonText('Rename');
button.onClick(async () => { button.onClick(async () => {
button.setDisabled(true); button.setDisabled(true);
await renameFilesInObsidian(this.app, this.plugin); await renameFilesInObsidian(this.app, this.plugin);
this.display(); this.reRenderPreview();
}); });
}); });
} }
@ -200,8 +269,31 @@ class BulkRenameSettingsTab extends PluginSettingTab {
renderDonateButton(this.containerEl); renderDonateButton(this.containerEl);
} }
calculateFiles() { reRenderPreview = () => {
this.plugin.settings.fileNames = getObsidianFiles(this.app, this.plugin); 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,
);
}
getFilesByTags() {
this.plugin.settings.fileNames = getObsidianFilesWithTagName(
this.app,
this.plugin,
);
} }
} }

3666
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,6 @@
"prettier": "2.7.1", "prettier": "2.7.1",
"ts-jest": "^28.0.8", "ts-jest": "^28.0.8",
"tslib": "2.4.0", "tslib": "2.4.0",
"typescript": "^4.7.4" "typescript": "4.8.2"
} }
} }

View File

@ -0,0 +1,45 @@
import {
getFilesNamesInDirectory,
getRenderedFileNamesReplaced,
syncScrolls,
} from '../services/file.service';
import { createPreviewElement } from './PreviewElement';
import BulkRenamePlugin, { BulkRenameSettingsTab } from '../../main';
export const renderPreviewFiles = (
setting: BulkRenameSettingsTab['filesAndPreview'],
plugin: BulkRenamePlugin,
state: BulkRenameSettingsTab['state'],
) => {
let existingFilesTextArea: HTMLTextAreaElement;
let replacedPreviewTextArea: HTMLTextAreaElement;
return setting
.clear()
.addTextArea((text) => {
text.setPlaceholder('Here you will see files under folder location');
text.setDisabled(true);
existingFilesTextArea = text.inputEl;
const value = getFilesNamesInDirectory(plugin);
text.setValue(value);
const previewLabel = createPreviewElement();
text.inputEl.insertAdjacentElement('afterend', previewLabel);
text.inputEl.addClass('templater_cmd');
})
.addTextArea((text) => {
text.setPlaceholder(
'How filenames will looks like after replacement(click preview first)',
);
text.setDisabled(true);
replacedPreviewTextArea = text.inputEl;
const value = getRenderedFileNamesReplaced(plugin);
text.setValue(value);
text.inputEl.addClass('templater_cmd');
})
.then((setting) => {
syncScrolls(existingFilesTextArea, replacedPreviewTextArea, state);
});
};

View File

@ -1,21 +1,6 @@
import { App, Notice, TFile } from 'obsidian'; import { App, Notice, TFile } from 'obsidian';
import BulkRenamePlugin, { State } from '../../main'; import BulkRenamePlugin, { State } from '../../main';
export const getObsidianFiles = (app: App, plugin: BulkRenamePlugin) => {
const { folderName } = plugin.settings;
const abstractFiles = app.vault.getAllLoadedFiles();
const files = [] as TFile[];
abstractFiles.forEach((file) => {
if (file instanceof TFile && file.parent.name.includes(folderName)) {
files.push(file);
}
});
const filesSortedByName = files.sort((a, b) => a.name.localeCompare(b.name));
return filesSortedByName;
};
export const getFilesNamesInDirectory = (plugin: BulkRenamePlugin) => { export const getFilesNamesInDirectory = (plugin: BulkRenamePlugin) => {
const { fileNames } = plugin.settings; const { fileNames } = plugin.settings;
@ -65,27 +50,33 @@ export const replaceFilePath = (plugin: BulkRenamePlugin, file: TFile) => {
return `${newPath}.${file.extension}`; return `${newPath}.${file.extension}`;
}; };
export const renameFilesInObsidian = async (app: App, plugin: BulkRenamePlugin) => { export const renameFilesInObsidian = async (
const { replacePattern, existingSymbol } = plugin.settings; app: App,
plugin: BulkRenamePlugin,
) => {
const { existingSymbol, fileNames } = plugin.settings;
if (!existingSymbol) { if (!existingSymbol) {
new Notice('please fill Existing Symbol'); new Notice('please fill Existing Symbol');
return; return;
} }
if (replacePattern === existingSymbol) { // if (replacePattern === existingSymbol) {
new Notice("Replace Pattern shouldn't much Existing Symbol"); // new Notice("Replace Pattern shouldn't much Existing Symbol");
return; // return;
} // }
if (!plugin.settings.fileNames.length) { if (!fileNames.length) {
new Notice('Please check your results before rename!'); new Notice('Please check your results before rename!');
return; return;
} }
new Notice('renaming has been started'); new Notice('renaming has been started');
for (const fileName of plugin.settings.fileNames) { for (const fileName of fileNames) {
await app.fileManager.renameFile(fileName, replaceFilePath(plugin, fileName)); await app.fileManager.renameFile(
fileName,
replaceFilePath(plugin, fileName),
);
} }
new Notice('successfully renamed all files'); new Notice('successfully renamed all files');
}; };

View File

@ -0,0 +1,51 @@
import { App, TFile } from 'obsidian';
import BulkRenamePlugin from '../../main';
export const getObsidianFilesByFolderName = (
app: App,
plugin: BulkRenamePlugin,
) => {
const { folderName } = plugin.settings;
const abstractFiles = app.vault.getAllLoadedFiles();
const files = abstractFiles.filter((file) => {
return file instanceof TFile && file.parent.name.includes(folderName);
});
const filesSortedByName = files.sort((a, b) => a.name.localeCompare(b.name));
return filesSortedByName;
};
export const getObsidianFilesWithTagName = (
app: App,
plugin: BulkRenamePlugin,
) => {
const { tags } = plugin.settings;
const abstractFiles = app.vault.getAllLoadedFiles();
const files = abstractFiles.filter((file) => {
if (!(file instanceof TFile)) {
return;
}
const fileMetadata = app.metadataCache.getFileCache(file);
if (!fileMetadata || !fileMetadata.tags) {
return;
}
const hasTagsInTheFile = fileMetadata.tags.find((fileTags) => {
return tags.includes(fileTags.tag);
});
if (!hasTagsInTheFile) {
return;
}
return file;
});
const filesSortedByName = files.sort((a, b) => a.name.localeCompare(b.name));
return filesSortedByName;
};