diff --git a/README.md b/README.md index bbeb27f..951e648 100644 --- a/README.md +++ b/README.md @@ -150,10 +150,11 @@ Thank you! - [x] Add excluded folder support - [x] Add excluded file support - [x] Add regex support for excluded folders and files (must support language accents like ñ, á, š, đ, こ, 猫, etc.) +- [x] Add time based execution of rule sorting +- [x] Exposing the move files button to the left toolbar +- [x] Exposing the move files to the commands accessible via command palette - [ ] Add a file like .gitignore which contains all the moving rules (i am assuming the list can grow quite big for some people) - [ ] Add import and export of rules -- [ ] Add time based execution of rule sorting -- [ ] Exposing the move files button to the left toolbar - [ ] Auto tagging of moved files with the destination folder name (last folder in the path) diff --git a/Settings/Settings.ts b/Settings/Settings.ts index 9b7095c..380ffc4 100644 --- a/Settings/Settings.ts +++ b/Settings/Settings.ts @@ -7,6 +7,8 @@ export interface AutoMoverSettings { // moveOnSave: boolean; movingRules: MovingRule[]; exclusionRules: ExclusionRule[]; + automaticMoving: boolean; + timer: number | null; // in miliseconds } export const DEFAULT_SETTINGS: Partial = { @@ -14,6 +16,8 @@ export const DEFAULT_SETTINGS: Partial = { // moveOnSave: true, movingRules: [], exclusionRules: [], + automaticMoving: false, + timer: null, }; function loadSettings( diff --git a/Settings/SettingsTab.ts b/Settings/SettingsTab.ts index ab834f9..04ba434 100644 --- a/Settings/SettingsTab.ts +++ b/Settings/SettingsTab.ts @@ -2,6 +2,7 @@ import type AutoMoverPlugin from "main"; import { ExclusionRule } from "Models/ExclusionRule"; import { MovingRule } from "Models/MovingRule"; import * as obsidian from "obsidian"; +import timerUtil from "Utils/TimerUtil"; export class SettingsTab extends obsidian.PluginSettingTab { plugin: AutoMoverPlugin; @@ -38,6 +39,44 @@ export class SettingsTab extends obsidian.PluginSettingTab { }); }); + const automaticMovingContainer = containerEl.createDiv({}); + + new obsidian.Setting(automaticMovingContainer) + .setName("Automatic moving") + .setDesc( + `Execute a timed event that goes through all the files and moves them according to the rules specified below. + The formatting is hh:mm:ss. + If the timer is set to 0, the automatic moving will do nothing.`, + ) + .setClass("timer-setting") + .addToggle((cb) => + cb + .setValue(this.plugin.settings.automaticMoving) + .onChange(async (value) => { + this.plugin.settings.automaticMoving = value; + await this.plugin.saveData(this.plugin.settings); + this.app.workspace.trigger( + "AutoMover:automatic-moving-update", + this.plugin.settings, + ); + this.display(); + }), + ) + .addText((cb) => + cb + .setDisabled(!this.plugin.settings.automaticMoving) + .setValue(timerUtil.formatTime(this.plugin.settings.timer)) + .setPlaceholder("hh:mm:ss") + .onChange(async (value) => { + this.plugin.settings.timer = timerUtil.parseTimeToMs(value); + await this.plugin.saveData(this.plugin.settings); + this.app.workspace.trigger( + "AutoMover:automatic-moving-update", + this.plugin.settings, + ); + }), + ); + // there is no default event to move on save, therefore, i'd need to define a new one // running move on change could be an option, but it would produce an overhead in performance because of constant checking for changes // diff --git a/Utils/ExclusionMatcherUtil.ts b/Utils/ExclusionMatcherUtil.ts index 2878c42..d7c9f46 100644 --- a/Utils/ExclusionMatcherUtil.ts +++ b/Utils/ExclusionMatcherUtil.ts @@ -20,12 +20,8 @@ class ExclusionMatcherUtil { * @returns true if the file path is excluded, false otherwise */ isFilePathExcluded(file: TFile, exclusionRules: ExclusionRule[]): boolean { - console.log("Exclusion rules: ", exclusionRules); for (const rule of exclusionRules) { const regex = new RegExp(rule.regex); - console.log("Regex: ", regex); - console.log("File path: ", file.path); - console.log("Result: ", regex.test(file.path)); if (regex.test(file.path)) { return true; } diff --git a/Utils/TimerUtil.ts b/Utils/TimerUtil.ts new file mode 100644 index 0000000..eb340d2 --- /dev/null +++ b/Utils/TimerUtil.ts @@ -0,0 +1,44 @@ +class TimerUtil { + private static instance: TimerUtil; + private timer: NodeJS.Timeout | null = null; + + private constructor() {} + + static getInstance(): TimerUtil { + if (!TimerUtil.instance) { + TimerUtil.instance = new TimerUtil(); + } + return TimerUtil.instance; + } + + formatTime(ms: number | null): string { + if (ms == null) return "00:00:00"; + const totalSeconds = Math.floor(ms / 1000); + const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, "0"); + const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart( + 2, + "0", + ); + const seconds = String(totalSeconds % 60).padStart(2, "0"); + return `${hours}:${minutes}:${seconds}`; + } + + parseTimeToMs(timeStr: string): number { + const [h, m, s] = timeStr.split(":").map(Number); + if ([h, m, s].some((n) => Number.isNaN(n))) return 0; + return ((h ?? 0) * 3600 + (m ?? 0) * 60 + (s ?? 0)) * 1000; + } + + startTimer(callback: () => void, interval: number) { + if (this.timer) { + // console.log("Clearing existing timer"); + clearInterval(this.timer); + } + + // console.log("Starting timer with interval: ", interval); + this.timer = setTimeout(callback, interval); + } +} + +const timerUtil = TimerUtil.getInstance(); +export default timerUtil; diff --git a/main.ts b/main.ts index 3109981..aec7cbc 100644 --- a/main.ts +++ b/main.ts @@ -4,6 +4,8 @@ import movingUtil from "Utils/MovingUtil"; import ruleMatcherUtil from "Utils/RuleMatcherUtil"; import * as obsidian from "obsidian"; import exclusionMatcherUtil from "Utils/ExclusionMatcherUtil"; +import timerUtil from "Utils/TimerUtil"; +import { addIcon } from "obsidian"; export default class AutoMoverPlugin extends obsidian.Plugin { settings: Settings.AutoMoverSettings; @@ -26,15 +28,47 @@ export default class AutoMoverPlugin extends obsidian.Plugin { if (!this.areThereRulesToApply()) return; if (!this.areRulesValid()) return; - if (this.settings.moveOnOpen) { - this.registerEvent( - this.app.workspace.on("file-open", (file: obsidian.TFile) => { - if (file == null || file.path == null) return; - if (this.isFileExcluded(file)) return; - this.matchAndMoveFile(file); - }), - ); - } + this.registerEvent( + this.app.workspace.on("file-open", (file: obsidian.TFile) => { + if (!this.settings.moveOnOpen) return; + if (file == null || file.path == null) return; + if (this.isFileExcluded(file)) return; + this.matchAndMoveFile(file); + }), + ); + + this.registerEvent( + // since i am defining my own event, ts-lint is crying about it but it works in the end + this.app.workspace.on("AutoMover:automatic-moving-update", () => { + // console.log("Automatic moving update"); + this.automaticMoving(); + }), + ); + + this.addCommand({ + id: "AutoMover:move-files", + name: "Move files", + callback: () => { + this.goThroughAllFiles(); + }, + }); + + this.addRibbonIcon("file-input", "AutoMover: Move files", () => { + this.goThroughAllFiles(); + }); + } + + /** + * Reocurring function that will be called by the timer + * + * @returns void + */ + automaticMoving() { + // console.log("Automatic moving run"); + if (!this.settings.automaticMoving) return; + if (this.settings.timer == null || this.settings.timer <= 0) return; + this.goThroughAllFiles(); + timerUtil.startTimer(this.automaticMoving.bind(this), this.settings.timer); } /** @@ -43,11 +77,10 @@ export default class AutoMoverPlugin extends obsidian.Plugin { * @returns void */ goThroughAllFiles() { + // console.log("Going through all files"); const files = this.app.vault.getFiles(); for (const file of files) { if (file == null || file.path == null) continue; - console.log("Going through file: ", file.path); - console.log("File excluded: ", this.isFileExcluded(file)); if (this.isFileExcluded(file)) continue; this.matchAndMoveFile(file); } @@ -77,7 +110,7 @@ export default class AutoMoverPlugin extends obsidian.Plugin { * @returns void */ matchAndMoveFile(file: obsidian.TFile): void { - console.log("Moving file: ", file.path); + // console.log("Moving file: ", file.path); const rule = ruleMatcherUtil.getMatchingRule( file, this.settings.movingRules, diff --git a/styles.css b/styles.css index 823ed84..43b9bbb 100644 --- a/styles.css +++ b/styles.css @@ -63,3 +63,16 @@ If your plugin does not need CSS, delete this file. .rule_button_duplicate:hover { background-color: var(--color-blue) !important; } + +/* Style for timer-setting input field */ +.setting-item.timer-setting input[type="text"] { + width: auto; + min-width: 8ch; + /* Minimum for hh:mm:ss */ + max-width: 16ch; + /* Allows up to ~9999:59:59 */ + /* padding: 2px 6px; */ + font-family: monospace; + text-align: center; + /* box-sizing: content-box; */ +}