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
## 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
- **Symbols in existing files** - the symbols/characters that we have in the files
- **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
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 { renderDonateButton } from './src/components/DonateButton';
import {
getFilesNamesInDirectory,
getObsidianFiles,
getRenderedFileNamesReplaced,
renameFilesInObsidian,
syncScrolls,
} from './src/services/file.service';
import { renameFilesInObsidian } from './src/services/file.service';
import { createPreviewElement } from './src/components/PreviewElement';
import {
getObsidianFilesByFolderName,
getObsidianFilesWithTagName,
} from './src/services/obsidian.service';
import { renderPreviewFiles } from './src/components/RenderPreviewFiles';
interface BulkRenamePluginSettings {
folderName: string;
fileNames: TFile[];
fileNames: TAbstractFile[];
existingSymbol: string;
replacePattern: string;
tags: string[];
viewType: 'tags' | 'folder';
}
const DEFAULT_SETTINGS: BulkRenamePluginSettings = {
@ -22,6 +30,15 @@ const DEFAULT_SETTINGS: BulkRenamePluginSettings = {
fileNames: [],
existingSymbol: '',
replacePattern: '',
tags: [],
viewType: 'folder',
};
const isViewTypeFolder = ({ settings }: BulkRenamePlugin) => {
return settings.viewType === 'folder';
};
const isViewTypeTags = ({ settings }: BulkRenamePlugin) => {
return settings.viewType === 'tags';
};
class BulkRenamePlugin extends Plugin {
@ -48,9 +65,10 @@ export type State = {
filesScroll: number;
};
class BulkRenameSettingsTab extends PluginSettingTab {
export class BulkRenameSettingsTab extends PluginSettingTab {
plugin: BulkRenamePlugin;
state: State;
filesAndPreview: Setting;
constructor(app: App, plugin: BulkRenamePlugin) {
super(app, plugin);
@ -66,52 +84,54 @@ 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();
this.renderTagNames();
this.renderReplaceSymbol();
this.renderFilesAndPreview();
this.renderRenameFiles();
this.renderSupportDevelopment();
}
renderReplaceSymbol() {
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);
renderTabs() {
new Setting(this.containerEl)
.setName('Existing Symbol')
.setDesc(desc)
.addText((textComponent) => {
const previewLabel = createPreviewElement('Replacement symbols');
textComponent.inputEl.insertAdjacentElement('afterend', previewLabel);
textComponent.setValue(settings.existingSymbol);
textComponent.setPlaceholder('existing symbols');
textComponent.onChange((newValue) => {
settings.existingSymbol = newValue;
this.plugin.saveSettings();
.setName('UI will be changed when you click those buttons')
.addButton((button) => {
button.setButtonText('Search by folder');
if (isViewTypeFolder(this.plugin)) {
button.setCta();
}
button.onClick(async () => {
this.plugin.settings.viewType = 'folder';
await this.plugin.saveSettings();
this.display();
});
})
.addText((textComponent) => {
textComponent.setValue(settings.replacePattern);
textComponent.setPlaceholder('replace with');
textComponent.onChange((newValue) => {
settings.replacePattern = newValue;
this.plugin.saveSettings();
this.calculateFiles();
.addButton((button) => {
button.setButtonText('Search By Tags');
if (isViewTypeTags(this.plugin)) {
button.setCta();
}
button.onClick(async () => {
this.plugin.settings.viewType = 'tags';
await this.plugin.saveSettings();
this.display();
});
});
}
renderFileLocation() {
if (!isViewTypeFolder(this.plugin)) {
return;
}
new Setting(this.containerEl)
.setName('Folder location')
.setDesc('Files in this folder will be available renamed.')
@ -122,54 +142,101 @@ 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.display();
});
button.onClick(this.reRenderPreview);
});
}
renderFilesAndPreview() {
let existingFilesTextArea: HTMLTextAreaElement;
let replacedPreviewTextArea: HTMLTextAreaElement;
renderTagNames() {
if (!isViewTypeTags(this.plugin)) {
return;
}
new Setting(this.containerEl)
.setName('Files within the folder')
.setDesc(`Total Files: ${this.plugin.settings.fileNames.length}`)
.addTextArea((text) => {
text.setPlaceholder('Here you will see files under folder location');
text.setDisabled(true);
existingFilesTextArea = text.inputEl;
.setName('Tag names ')
.setDesc('all files with the tags will be found')
.addSearch((cb) => {
// @ts-ignore
cb.inputEl.addEventListener('keydown', (event) => {
if (event.key !== 'Enter') {
return;
}
const target = event.target as HTMLInputElement;
const value = getFilesNamesInDirectory(this.plugin);
text.setValue(value);
const previewLabel = createPreviewElement();
text.inputEl.insertAdjacentElement('afterend', previewLabel);
text.inputEl.addClass('templater_cmd');
this.plugin.settings.tags = target.value.replace(/ /g, '').split(',');
this.plugin.saveSettings();
});
cb.setPlaceholder('Example: #tag, #tag2')
.setValue(this.plugin.settings.tags.join(','))
.onChange((newFolder) => {
this.plugin.settings.tags = newFolder.replace(/ /g, '').split(',');
this.plugin.saveSettings();
this.getFilesByTags();
});
// @ts-ignore
cb.containerEl.addClass('templater_search');
})
.addTextArea((text) => {
text.setPlaceholder(
'How filenames will looks like after replacement(click preview first)',
);
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);
.addButton((button) => {
button.setButtonText('Refresh');
button.onClick(this.reRenderPreview);
});
}
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() {
const desc = document.createDocumentFragment();
desc.append(
@ -186,12 +253,14 @@ class BulkRenameSettingsTab extends PluginSettingTab {
.setName('Replace patterns')
.addButton((button) => {
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.onClick(async () => {
button.setDisabled(true);
await renameFilesInObsidian(this.app, this.plugin);
this.display();
this.reRenderPreview();
});
});
}
@ -200,8 +269,31 @@ class BulkRenameSettingsTab extends PluginSettingTab {
renderDonateButton(this.containerEl);
}
calculateFiles() {
this.plugin.settings.fileNames = getObsidianFiles(this.app, this.plugin);
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,
);
}
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",
"ts-jest": "^28.0.8",
"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 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) => {
const { fileNames } = plugin.settings;
@ -65,27 +50,33 @@ export const replaceFilePath = (plugin: BulkRenamePlugin, file: TFile) => {
return `${newPath}.${file.extension}`;
};
export const renameFilesInObsidian = async (app: App, plugin: BulkRenamePlugin) => {
const { replacePattern, existingSymbol } = plugin.settings;
export const renameFilesInObsidian = async (
app: App,
plugin: BulkRenamePlugin,
) => {
const { existingSymbol, fileNames } = plugin.settings;
if (!existingSymbol) {
new Notice('please fill Existing Symbol');
return;
}
if (replacePattern === existingSymbol) {
new Notice("Replace Pattern shouldn't much Existing Symbol");
return;
}
// if (replacePattern === existingSymbol) {
// new Notice("Replace Pattern shouldn't much Existing Symbol");
// return;
// }
if (!plugin.settings.fileNames.length) {
if (!fileNames.length) {
new Notice('Please check your results before rename!');
return;
}
new Notice('renaming has been started');
for (const fileName of plugin.settings.fileNames) {
await app.fileManager.renameFile(fileName, replaceFilePath(plugin, fileName));
for (const fileName of fileNames) {
await app.fileManager.renameFile(
fileName,
replaceFilePath(plugin, fileName),
);
}
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;
};