obsidian-sample-plugin/main.ts

250 lines
7.0 KiB
TypeScript

import { App, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian';
import { FolderSuggest } from './suggestions/folderSuggest';
interface MyPluginSettings {
folderName: string;
fileNames: TFile[];
existingSymbol: string;
replacePattern: string;
}
const DEFAULT_SETTINGS: MyPluginSettings = {
folderName: '',
fileNames: [],
existingSymbol: '',
replacePattern: '',
};
export default class MyPlugin extends Plugin {
settings: MyPluginSettings;
async onload() {
await this.loadSettings();
this.addSettingTab(new BulkRenameSettingsTab(this.app, this));
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
}
type State = {
previewScroll: number;
filesScroll: number;
};
class BulkRenameSettingsTab extends PluginSettingTab {
plugin: MyPlugin;
state: State;
constructor(app: App, plugin: MyPlugin) {
super(app, plugin);
this.state = {
previewScroll: 0,
filesScroll: 0,
};
this.plugin = plugin;
}
display() {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl('h2', { text: 'General Settings' });
this.renderFileLocation();
this.renderReplaceSymbol();
this.renderFilesAndPreview();
this.renderRenameFiles();
}
renderReplaceSymbol() {
const { settings } = this.plugin;
new Setting(this.containerEl)
.setName('Replace pattern')
.setDesc('Files in this folder will be available renamed.')
.addText((textComponent) => {
textComponent.setValue(settings.existingSymbol);
textComponent.setPlaceholder('existing symbols');
textComponent.onChange((newValue) => {
settings.existingSymbol = newValue;
this.plugin.saveSettings();
});
})
.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('Preview');
button.onClick(() => {
this.display();
});
});
}
renderFileLocation() {
new Setting(this.containerEl)
.setName('Folder location')
.setDesc('Files in this folder will be available renamed.')
.addSearch((cb) => {
new FolderSuggest(this.app, cb.inputEl);
cb.setPlaceholder('Example: folder1/')
.setValue(this.plugin.settings.folderName)
.onChange((newFolder) => {
this.plugin.settings.folderName = newFolder;
this.plugin.saveSettings();
this.calculateFiles();
});
// @ts-ignore
cb.containerEl.addClass('templater_search');
})
.addButton((button) => {
button.setButtonText('Refresh');
button.onClick(() => {
this.display();
});
});
}
renderFilesAndPreview() {
let existingFilesTextArea: HTMLTextAreaElement;
let replacedPreviewTextArea: HTMLTextAreaElement;
new Setting(this.containerEl)
.setName('files within the folder')
.setDesc(`Total Files: ${this.plugin.settings.fileNames.length}`)
.addTextArea((text) => {
existingFilesTextArea = text.inputEl;
const value = getRenderedFileNames(this.plugin);
text.setValue(value);
text.setDisabled(true);
const previewLabel = createPreviewElement();
text.inputEl.insertAdjacentElement('afterend', previewLabel);
text.inputEl.addClass('templater_cmd');
})
.addTextArea((text) => {
replacedPreviewTextArea = text.inputEl;
const value = getRenderedFileNamesReplaced(this.plugin);
text.setValue(value);
text.setDisabled(true);
text.inputEl.addClass('templater_cmd');
})
.then((setting) => {
syncScrolls(existingFilesTextArea, replacedPreviewTextArea, this.state);
});
}
renderRenameFiles() {
new Setting(this.containerEl)
.setName('Replace pattern')
.setDesc('Files in this folder will be available renamed.')
.addButton((button) => {
button.setButtonText('Rename');
button.onClick(() => {
const { replacePattern, existingSymbol } = this.plugin.settings;
if (!replacePattern || !existingSymbol) {
return;
}
this.plugin.settings.fileNames.forEach((fileName) => {
this.app.fileManager.renameFile(
fileName,
replaceFilePath(this.plugin, fileName),
);
});
});
});
}
calculateFiles() {
this.plugin.settings.fileNames = [
...getObsidianFiles(this.app, this.plugin.settings.folderName),
];
}
}
const getObsidianFiles = (app: App, folderName: string) => {
const abstractFiles = app.vault.getAllLoadedFiles();
const files = [] as TFile[];
abstractFiles.forEach((file) => {
if (file instanceof TFile && file.parent.name.includes(folderName)) {
files.push(file);
}
});
return sortByname(files);
};
const sortByname = (files: TFile[]) => {
return files.sort((a, b) => a.name.localeCompare(b.name));
};
const getRenderedFileNames = (plugin: MyPlugin) => {
return prepareFileNameString(plugin.settings.fileNames);
};
const prepareFileNameString = (filesNames: TFile[]) => {
let value = '';
filesNames.forEach((fileName, index) => {
const isLast = index + 1 === filesNames.length;
if (isLast) {
return (value += fileName.path);
}
value += fileName.path + '\r\n';
});
return value;
};
const getRenderedFileNamesReplaced = (plugin: MyPlugin) => {
const { fileNames } = plugin.settings;
const newFiles = fileNames.map((file) => {
return {
...file,
path: replaceFilePath(plugin, file),
};
});
return prepareFileNameString(newFiles);
};
const replaceFilePath = (plugin: MyPlugin, file: TFile) => {
const { replacePattern, existingSymbol } = plugin.settings;
return file.path.replaceAll(existingSymbol, replacePattern);
};
const createPreviewElement = () => {
const previewLabel = window.document.createElement('span');
previewLabel.className = 'previewLabel';
previewLabel.textContent = '=> => => =>';
previewLabel.style.margin = '0 20px';
return previewLabel;
};
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;
}
});
};