add jest
This commit is contained in:
parent
6e45036c19
commit
697c78f6cc
46
.eslintrc
46
.eslintrc
|
@ -1,23 +1,25 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": { "node": true },
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off"
|
||||
}
|
||||
}
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"jest/globals": true
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "jest"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +1,44 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from 'builtin-modules'
|
||||
import esbuild from 'esbuild';
|
||||
import process from 'process';
|
||||
import builtins from 'builtin-modules';
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
const banner = `/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
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({
|
||||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ['main.ts'],
|
||||
bundle: true,
|
||||
external: [
|
||||
'obsidian',
|
||||
'electron',
|
||||
'@codemirror/autocomplete',
|
||||
'@codemirror/collab',
|
||||
'@codemirror/commands',
|
||||
'@codemirror/language',
|
||||
'@codemirror/lint',
|
||||
'@codemirror/search',
|
||||
'@codemirror/state',
|
||||
'@codemirror/view',
|
||||
'@lezer/common',
|
||||
'@lezer/highlight',
|
||||
'@lezer/lr',
|
||||
...builtins],
|
||||
format: 'cjs',
|
||||
watch: !prod,
|
||||
target: 'es2018',
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : 'inline',
|
||||
treeShaking: true,
|
||||
outfile: 'main.js',
|
||||
}).catch(() => process.exit(1));
|
||||
esbuild
|
||||
.build({
|
||||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ['main.ts'],
|
||||
bundle: true,
|
||||
external: [
|
||||
'obsidian',
|
||||
'electron',
|
||||
'@codemirror/autocomplete',
|
||||
'@codemirror/collab',
|
||||
'@codemirror/commands',
|
||||
'@codemirror/language',
|
||||
'@codemirror/lint',
|
||||
'@codemirror/search',
|
||||
'@codemirror/state',
|
||||
'@codemirror/view',
|
||||
'@lezer/common',
|
||||
'@lezer/highlight',
|
||||
'@lezer/lr',
|
||||
...builtins,
|
||||
],
|
||||
format: 'cjs',
|
||||
watch: !prod,
|
||||
target: 'es2018',
|
||||
logLevel: 'info',
|
||||
sourcemap: prod ? false : 'inline',
|
||||
treeShaking: true,
|
||||
outfile: 'main.js',
|
||||
})
|
||||
.catch(() => process.exit(1));
|
||||
|
|
145
main.ts
145
main.ts
|
@ -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 { renderDonateButton } from './src/components/DonateButton';
|
||||
import {
|
||||
getFilesNamesInDirectory,
|
||||
getObsidianFiles,
|
||||
getRenderedFileNamesReplaced,
|
||||
renameFilesInObsidian,
|
||||
syncScrolls,
|
||||
} from './src/services/file.service';
|
||||
import { createPreviewElement } from './src/components/PreviewElement';
|
||||
|
||||
interface MyPluginSettings {
|
||||
folderName: string;
|
||||
|
@ -16,31 +31,38 @@ const DEFAULT_SETTINGS: MyPluginSettings = {
|
|||
replacePattern: '',
|
||||
};
|
||||
|
||||
export default class MyPlugin extends Plugin {
|
||||
class BulkRenamePlugin extends Plugin {
|
||||
settings: MyPluginSettings;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
this.addSettingTab(new BulkRenameSettingsTab(this.app, this));
|
||||
|
||||
// this.app.vault.on('rename', (file, oldPath) => {
|
||||
// });
|
||||
}
|
||||
|
||||
onunload() {}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
type State = {
|
||||
export type State = {
|
||||
previewScroll: number;
|
||||
filesScroll: number;
|
||||
};
|
||||
|
||||
class BulkRenameSettingsTab extends PluginSettingTab {
|
||||
plugin: MyPlugin;
|
||||
plugin: BulkRenamePlugin;
|
||||
state: State;
|
||||
|
||||
constructor(app: App, plugin: MyPlugin) {
|
||||
constructor(app: App, plugin: BulkRenamePlugin) {
|
||||
super(app, plugin);
|
||||
this.state = {
|
||||
previewScroll: 0,
|
||||
|
@ -54,6 +76,7 @@ class BulkRenameSettingsTab extends PluginSettingTab {
|
|||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
containerEl.createEl('h2', { text: 'Bulk Rename - Settings' });
|
||||
|
||||
this.renderFileLocation();
|
||||
this.renderReplaceSymbol();
|
||||
this.renderFilesAndPreview();
|
||||
|
@ -75,7 +98,7 @@ class BulkRenameSettingsTab extends PluginSettingTab {
|
|||
desc.appendChild(button);
|
||||
|
||||
new Setting(this.containerEl)
|
||||
.setName('Symbols in existing file name')
|
||||
.setName('Existing Symbol')
|
||||
.setDesc(desc)
|
||||
.addText((textComponent) => {
|
||||
const previewLabel = createPreviewElement('Replacement symbols');
|
||||
|
@ -131,10 +154,12 @@ class BulkRenameSettingsTab extends PluginSettingTab {
|
|||
.setDesc(`Total Files: ${this.plugin.settings.fileNames.length}`)
|
||||
.addTextArea((text) => {
|
||||
text.setPlaceholder('Here you will see files under folder location');
|
||||
existingFilesTextArea = text.inputEl;
|
||||
const value = getRenderedFileNames(this.plugin);
|
||||
text.setValue(value);
|
||||
text.setDisabled(true);
|
||||
existingFilesTextArea = text.inputEl;
|
||||
|
||||
const value = getFilesNamesInDirectory(this.plugin);
|
||||
text.setValue(value);
|
||||
|
||||
const previewLabel = createPreviewElement();
|
||||
text.inputEl.insertAdjacentElement('afterend', previewLabel);
|
||||
text.inputEl.addClass('templater_cmd');
|
||||
|
@ -143,10 +168,11 @@ class BulkRenameSettingsTab extends PluginSettingTab {
|
|||
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.setDisabled(true);
|
||||
text.inputEl.addClass('templater_cmd');
|
||||
})
|
||||
.then((setting) => {
|
||||
|
@ -172,18 +198,10 @@ class BulkRenameSettingsTab extends PluginSettingTab {
|
|||
button.buttonEl.style.width = '100%';
|
||||
button.setTooltip("FYI: We don't have undone button yet!");
|
||||
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),
|
||||
);
|
||||
});
|
||||
button.onClick(async () => {
|
||||
button.setDisabled(true);
|
||||
renameFilesInObsidian(this.app, this.plugin);
|
||||
this.display();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -193,87 +211,8 @@ class BulkRenameSettingsTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
calculateFiles() {
|
||||
this.plugin.settings.fileNames = [
|
||||
...getObsidianFiles(this.app, this.plugin.settings.folderName),
|
||||
];
|
||||
this.plugin.settings.fileNames = getObsidianFiles(this.app, this.plugin);
|
||||
}
|
||||
}
|
||||
|
||||
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 = (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;
|
||||
}
|
||||
});
|
||||
};
|
||||
export default BulkRenamePlugin;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
@ -6,6 +6,8 @@
|
|||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"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"
|
||||
},
|
||||
"keywords": [],
|
||||
|
@ -15,14 +17,18 @@
|
|||
"@popperjs/core": "^2.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^28.1.8",
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.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",
|
||||
"prettier": "2.7.1",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4",
|
||||
"prettier": "2.7.1"
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
};
|
|
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
export {};
|
|
@ -4,14 +4,19 @@
|
|||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"target": "es2018",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"downlevelIteration": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": false,
|
||||
"strictNullChecks": true,
|
||||
"lib": ["DOM", "ES5", "ES6", "ES7", "ES2021"]
|
||||
},
|
||||
"typeRoots": ["node_modules/@types", "obsidian"],
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue