This commit is contained in:
Oleg 2022-08-28 12:39:54 +03:00
parent 6e45036c19
commit 697c78f6cc
10 changed files with 18206 additions and 410 deletions

View File

@ -1,23 +1,25 @@
{ {
"root": true, "root": true,
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"env": { "node": true }, "env": {
"plugins": [ "browser": true,
"@typescript-eslint" "node": true,
], "jest/globals": true
"extends": [ },
"eslint:recommended", "plugins": ["@typescript-eslint", "jest"],
"plugin:@typescript-eslint/eslint-recommended", "extends": [
"plugin:@typescript-eslint/recommended" "eslint:recommended",
], "plugin:@typescript-eslint/eslint-recommended",
"parserOptions": { "plugin:@typescript-eslint/recommended"
"sourceType": "module" ],
}, "parserOptions": {
"rules": { "sourceType": "module"
"no-unused-vars": "off", },
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], "rules": {
"@typescript-eslint/ban-ts-comment": "off", "no-unused-vars": "off",
"no-prototype-builtins": "off", "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
"@typescript-eslint/no-empty-function": "off" "@typescript-eslint/ban-ts-comment": "off",
} "no-prototype-builtins": "off",
"@typescript-eslint/no-empty-function": "off"
} }
}

View File

@ -1,42 +1,44 @@
import esbuild from "esbuild"; import esbuild from 'esbuild';
import process from "process"; import process from 'process';
import builtins from 'builtin-modules' import builtins from 'builtin-modules';
const banner = const banner = `/*
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin if you want to view the source, please visit the github repository of this plugin
*/ */
`; `;
const prod = (process.argv[2] === 'production'); const prod = process.argv[2] === 'production';
esbuild.build({ esbuild
banner: { .build({
js: banner, banner: {
}, js: banner,
entryPoints: ['main.ts'], },
bundle: true, entryPoints: ['main.ts'],
external: [ bundle: true,
'obsidian', external: [
'electron', 'obsidian',
'@codemirror/autocomplete', 'electron',
'@codemirror/collab', '@codemirror/autocomplete',
'@codemirror/commands', '@codemirror/collab',
'@codemirror/language', '@codemirror/commands',
'@codemirror/lint', '@codemirror/language',
'@codemirror/search', '@codemirror/lint',
'@codemirror/state', '@codemirror/search',
'@codemirror/view', '@codemirror/state',
'@lezer/common', '@codemirror/view',
'@lezer/highlight', '@lezer/common',
'@lezer/lr', '@lezer/highlight',
...builtins], '@lezer/lr',
format: 'cjs', ...builtins,
watch: !prod, ],
target: 'es2018', format: 'cjs',
logLevel: "info", watch: !prod,
sourcemap: prod ? false : 'inline', target: 'es2018',
treeShaking: true, logLevel: 'info',
outfile: 'main.js', sourcemap: prod ? false : 'inline',
}).catch(() => process.exit(1)); treeShaking: true,
outfile: 'main.js',
})
.catch(() => process.exit(1));

145
main.ts
View File

@ -1,6 +1,21 @@
import { App, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian'; import {
App,
Notice,
Plugin,
PluginSettingTab,
Setting,
TFile,
} 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 {
getFilesNamesInDirectory,
getObsidianFiles,
getRenderedFileNamesReplaced,
renameFilesInObsidian,
syncScrolls,
} from './src/services/file.service';
import { createPreviewElement } from './src/components/PreviewElement';
interface MyPluginSettings { interface MyPluginSettings {
folderName: string; folderName: string;
@ -16,31 +31,38 @@ const DEFAULT_SETTINGS: MyPluginSettings = {
replacePattern: '', replacePattern: '',
}; };
export default class MyPlugin extends Plugin { class BulkRenamePlugin extends Plugin {
settings: MyPluginSettings; settings: MyPluginSettings;
async onload() { async onload() {
await this.loadSettings(); await this.loadSettings();
this.addSettingTab(new BulkRenameSettingsTab(this.app, this)); this.addSettingTab(new BulkRenameSettingsTab(this.app, this));
// this.app.vault.on('rename', (file, oldPath) => {
// });
} }
onunload() {}
async loadSettings() { async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
} }
async saveSettings() { async saveSettings() {
await this.saveData(this.settings); await this.saveData(this.settings);
} }
} }
type State = { export type State = {
previewScroll: number; previewScroll: number;
filesScroll: number; filesScroll: number;
}; };
class BulkRenameSettingsTab extends PluginSettingTab { class BulkRenameSettingsTab extends PluginSettingTab {
plugin: MyPlugin; plugin: BulkRenamePlugin;
state: State; state: State;
constructor(app: App, plugin: MyPlugin) { constructor(app: App, plugin: BulkRenamePlugin) {
super(app, plugin); super(app, plugin);
this.state = { this.state = {
previewScroll: 0, previewScroll: 0,
@ -54,6 +76,7 @@ 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' });
this.renderFileLocation(); this.renderFileLocation();
this.renderReplaceSymbol(); this.renderReplaceSymbol();
this.renderFilesAndPreview(); this.renderFilesAndPreview();
@ -75,7 +98,7 @@ class BulkRenameSettingsTab extends PluginSettingTab {
desc.appendChild(button); desc.appendChild(button);
new Setting(this.containerEl) new Setting(this.containerEl)
.setName('Symbols in existing file name') .setName('Existing Symbol')
.setDesc(desc) .setDesc(desc)
.addText((textComponent) => { .addText((textComponent) => {
const previewLabel = createPreviewElement('Replacement symbols'); const previewLabel = createPreviewElement('Replacement symbols');
@ -131,10 +154,12 @@ class BulkRenameSettingsTab extends PluginSettingTab {
.setDesc(`Total Files: ${this.plugin.settings.fileNames.length}`) .setDesc(`Total Files: ${this.plugin.settings.fileNames.length}`)
.addTextArea((text) => { .addTextArea((text) => {
text.setPlaceholder('Here you will see files under folder location'); text.setPlaceholder('Here you will see files under folder location');
existingFilesTextArea = text.inputEl;
const value = getRenderedFileNames(this.plugin);
text.setValue(value);
text.setDisabled(true); text.setDisabled(true);
existingFilesTextArea = text.inputEl;
const value = getFilesNamesInDirectory(this.plugin);
text.setValue(value);
const previewLabel = createPreviewElement(); const previewLabel = createPreviewElement();
text.inputEl.insertAdjacentElement('afterend', previewLabel); text.inputEl.insertAdjacentElement('afterend', previewLabel);
text.inputEl.addClass('templater_cmd'); text.inputEl.addClass('templater_cmd');
@ -143,10 +168,11 @@ class BulkRenameSettingsTab extends PluginSettingTab {
text.setPlaceholder( text.setPlaceholder(
'How filenames will looks like after replacement(click preview first)', 'How filenames will looks like after replacement(click preview first)',
); );
text.setDisabled(true);
replacedPreviewTextArea = text.inputEl; replacedPreviewTextArea = text.inputEl;
const value = getRenderedFileNamesReplaced(this.plugin); const value = getRenderedFileNamesReplaced(this.plugin);
text.setValue(value); text.setValue(value);
text.setDisabled(true);
text.inputEl.addClass('templater_cmd'); text.inputEl.addClass('templater_cmd');
}) })
.then((setting) => { .then((setting) => {
@ -172,18 +198,10 @@ class BulkRenameSettingsTab extends PluginSettingTab {
button.buttonEl.style.width = '100%'; button.buttonEl.style.width = '100%';
button.setTooltip("FYI: We don't have undone button yet!"); button.setTooltip("FYI: We don't have undone button yet!");
button.setButtonText('Rename'); button.setButtonText('Rename');
button.onClick(() => { button.onClick(async () => {
const { replacePattern, existingSymbol } = this.plugin.settings; button.setDisabled(true);
if (!replacePattern || !existingSymbol) { renameFilesInObsidian(this.app, this.plugin);
return; this.display();
}
this.plugin.settings.fileNames.forEach((fileName) => {
this.app.fileManager.renameFile(
fileName,
replaceFilePath(this.plugin, fileName),
);
});
}); });
}); });
} }
@ -193,87 +211,8 @@ class BulkRenameSettingsTab extends PluginSettingTab {
} }
calculateFiles() { calculateFiles() {
this.plugin.settings.fileNames = [ this.plugin.settings.fileNames = getObsidianFiles(this.app, this.plugin);
...getObsidianFiles(this.app, this.plugin.settings.folderName),
];
} }
} }
const getObsidianFiles = (app: App, folderName: string) => { export default BulkRenamePlugin;
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 = (textContent = '=> => => =>') => {
const previewLabel = window.document.createElement('span');
previewLabel.className = 'previewLabel';
previewLabel.textContent = 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;
}
});
};

18180
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,8 @@
"scripts": { "scripts": {
"dev": "node esbuild.config.mjs", "dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"test": "jest ./src",
"test:watch": "jest ./src --watch",
"version": "node version-bump.mjs && git add manifest.json versions.json && git push origin --tags" "version": "node version-bump.mjs && git add manifest.json versions.json && git push origin --tags"
}, },
"keywords": [], "keywords": [],
@ -15,14 +17,18 @@
"@popperjs/core": "^2.11.2" "@popperjs/core": "^2.11.2"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^28.1.8",
"@types/node": "^16.11.6", "@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0", "@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0", "@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0", "builtin-modules": "3.3.0",
"esbuild": "0.14.47", "esbuild": "0.15.5",
"esbuild-jest": "^0.5.0",
"eslint-plugin-jest": "^26.8.7",
"jest": "29.0.1",
"obsidian": "latest", "obsidian": "latest",
"prettier": "2.7.1",
"tslib": "2.4.0", "tslib": "2.4.0",
"typescript": "4.7.4", "typescript": "^4.7.4"
"prettier": "2.7.1"
} }
} }

View File

@ -0,0 +1,7 @@
export const createPreviewElement = (textContent = '=> => => =>') => {
const previewLabel = window.document.createElement('span');
previewLabel.className = 'previewLabel';
previewLabel.textContent = textContent;
previewLabel.style.margin = '0 20px';
return previewLabel;
};

View File

@ -0,0 +1,100 @@
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;
return getFilesAsString(fileNames);
};
const getFilesAsString = (fileNames: TFile[]) => {
let value = '';
fileNames.forEach((fileName, index) => {
const isLast = index + 1 === fileNames.length;
if (isLast) {
return (value += fileName.path);
}
value += fileName.path + '\r\n';
});
return value;
};
export const getRenderedFileNamesReplaced = (plugin: BulkRenamePlugin) => {
const newFiles = selectFilenamesWithReplacedPath(plugin);
return getFilesAsString(newFiles);
};
const selectFilenamesWithReplacedPath = (plugin: BulkRenamePlugin) => {
const { fileNames } = plugin.settings;
return fileNames.map((file) => {
return {
...file,
path: replaceFilePath(plugin, file),
};
});
};
export const replaceFilePath = (plugin: BulkRenamePlugin, file: TFile) => {
const { replacePattern, existingSymbol } = plugin.settings;
return file.path.replaceAll(existingSymbol, replacePattern);
};
export const renameFilesInObsidian = (app: App, plugin: BulkRenamePlugin) => {
const { replacePattern, existingSymbol } = plugin.settings;
if (!existingSymbol) {
new Notice('please fill Existing Symbol');
return;
}
if (replacePattern === existingSymbol) {
new Notice("Replace Pattern shouldn't much Existing Symbol");
return;
}
new Notice('renaming has been started');
for (const fileName of plugin.settings.fileNames) {
app.fileManager.renameFile(fileName, replaceFilePath(plugin, fileName));
}
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;
}
});
};

View File

@ -0,0 +1,44 @@
import { TFile } from 'obsidian';
import { replaceFilePath } from './file.service';
import BulkRenamePlugin from '../../main';
describe('File Services', () => {
describe('Notifications', () => {
it.todo('should display notification before renaming');
it.todo('should display notification after renaming');
});
describe('Validation', () => {
it.todo('should display notification if there an error');
it.todo('should display notification if there an error');
it.todo("should display notification if Existing Symbol doesn't exists");
it.todo(
'should display notification if Existing Symbol match Replace Pattern',
);
});
describe('Renaming', () => {
it('should not rename extensions', () => {
const plugin = {
settings: {
replacePattern: '-',
existingSymbol: '.',
},
} as unknown as BulkRenamePlugin;
const file = {
path: '2022.10.13.md',
} as unknown as TFile;
const expectedResult = '2022-10-13.md';
const result = replaceFilePath(plugin, file);
expect(result).toEqual(expectedResult);
});
it.todo('should replace symbols in naming');
it.todo(
'Should rename files only in a particular directory if the name could match other directories',
);
});
});

View File

@ -0,0 +1 @@
export {};

View File

@ -4,14 +4,19 @@
"inlineSourceMap": true, "inlineSourceMap": true,
"inlineSources": true, "inlineSources": true,
"module": "ESNext", "module": "ESNext",
"target": "ES6", "target": "es2018",
"allowJs": true, "allowJs": true,
"noImplicitAny": true, "noImplicitAny": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"moduleResolution": "node", "moduleResolution": "node",
"importHelpers": true, "importHelpers": true,
"isolatedModules": true, "downlevelIteration": true,
"esModuleInterop": true,
"isolatedModules": false,
"strictNullChecks": true, "strictNullChecks": true,
"lib": ["DOM", "ES5", "ES6", "ES7", "ES2021"] "lib": ["DOM", "ES5", "ES6", "ES7", "ES2021"]
}, },
"typeRoots": ["node_modules/@types", "obsidian"],
"include": ["**/*.ts"] "include": ["**/*.ts"]
} }