add find-by tag and to a directory functionality
This commit is contained in:
parent
691e8855f5
commit
863e430134
19
README.md
19
README.md
|
@ -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
|
||||
|
||||
[](https://www.buymeacoffee.com/oleglustenko)
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 44 KiB |
246
main.ts
246
main.ts
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
};
|
|
@ -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');
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
Loading…
Reference in New Issue