From 39275566630125a32d9bd8ac9bfd0dcbbd81c06f Mon Sep 17 00:00:00 2001 From: Al0cam Date: Fri, 7 Nov 2025 17:59:47 +0100 Subject: [PATCH] feat: project rules implemented, readme remaining --- README.md | 8 +++++-- Settings/ProjectSection.ts | 2 +- Utils/ProjectMatcherUtil.ts | 43 +++++++++++++++++++----------------- Utils/PropertyUtil.ts | 43 ------------------------------------ Utils/RuleMatcherUtil.ts | 1 + main.ts | 44 +++++++++++++++++++++++++++++++++++-- manifest.json | 2 +- package.json | 2 +- versions.json | 2 +- 9 files changed, 76 insertions(+), 71 deletions(-) delete mode 100644 Utils/PropertyUtil.ts diff --git a/README.md b/README.md index ad0696a..107fd9a 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,10 @@ Other examples could include: 2. 00:00:05 -> triggers every five seconds 3. 72:30:00 -> triggers every 3 days and 30 minutes (if your Device is online and obsidian runnning for that long) +### Project Rules UI +From patch 1.0.7 onwards, you can use project rules to group moving rules together. +And to move files based on the project they belong to. +Project rules take precedence over the normal moving rules considering they are way more specific. ### Tag rules UI From patch 1.0.6 onwards, you can move files using tags. @@ -210,5 +214,5 @@ Thank you! - [x] Add Project moving rules - [x] Project name and destination path - [x] Subfield that contain the moving rules for the project -- [ ] Add project rules UI -- [ ] Project rules business logic +- [x] Add project rules UI +- [x] Project rules business logic diff --git a/Settings/ProjectSection.ts b/Settings/ProjectSection.ts index ef588fb..dc0fbf0 100644 --- a/Settings/ProjectSection.ts +++ b/Settings/ProjectSection.ts @@ -125,7 +125,7 @@ export function projectSection(containerEl: HTMLElement, plugin: AutoMoverPlugin value: rule.folder, cls: "rule_input", }).onchange = (e) => { - project.folder = (e.target as HTMLInputElement).value; + rule.folder = (e.target as HTMLInputElement).value; project.rules.map((r) => (r === rule ? rule : r)); plugin.saveData(plugin.settings); }; diff --git a/Utils/ProjectMatcherUtil.ts b/Utils/ProjectMatcherUtil.ts index d01b616..7f55909 100644 --- a/Utils/ProjectMatcherUtil.ts +++ b/Utils/ProjectMatcherUtil.ts @@ -1,6 +1,4 @@ import { ProjectRule } from "Models/ProjectRule"; -import type { TFile } from "obsidian"; -import propertyUtil from "./PropertyUtil"; class ProjectMatcherUtil { private static instance: ProjectMatcherUtil; @@ -17,29 +15,34 @@ class ProjectMatcherUtil { /** * Returns the first project rule that matches the file * If no rule matches, returns null - * @param file + * @param projectName * @param projectRules * @returns ProjectRule | null */ - public getMatchingProjectRule(file: TFile, projectRules: ProjectRule[]): ProjectRule | null { - propertyUtil.getPropertiesFromFile(file); - for (const rule of projectRules) { - if (rule.projectName == null || rule.projectName === "") { - console.error("Project Rule does not have a project name: ", rule); - continue; - } - if (rule.folder == null || rule.folder === "") { - console.error("Project Rule does not have a destination folder: ", rule); - continue; - } - // Check if the file path contains the project name as a wiki link or plain text - const wikiLink = `[[${rule.projectName}]]`; - if (file.path.includes(wikiLink) || file.path.includes(rule.projectName)) { - return rule; + public getMatchingProjectRule(projectName: string, projectRules: ProjectRule[]): ProjectRule | null { + for (const projectRule of projectRules) { + if (projectRule.projectName === projectName) { + return projectRule; } } return null; } + + /** + * Prepends the project folder to the destination path + * and checks whether the project folder ends with a slash + * + * @param projectRule + * @param subPath + * @returns string + */ + public constructProjectDestinationPath(projectRule: ProjectRule, subPath: string): string { + let projectFolder = projectRule.folder; + if (!projectFolder.endsWith("/")) { + projectFolder += "/"; + } + return projectFolder + subPath; + } } -const exclusionMatcherUtil = ProjectMatcherUtil.getInstance(); -export default exclusionMatcherUtil; +const projectMatcherUtil = ProjectMatcherUtil.getInstance(); +export default projectMatcherUtil; diff --git a/Utils/PropertyUtil.ts b/Utils/PropertyUtil.ts deleted file mode 100644 index efda3a1..0000000 --- a/Utils/PropertyUtil.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { TFile } from "obsidian"; - -class PropertyUtil { - private static instance: PropertyUtil; - - private constructor() {} - - static getInstance(): PropertyUtil { - if (!PropertyUtil.instance) { - PropertyUtil.instance = new PropertyUtil(); - } - return PropertyUtil.instance; - } - - /** - * TODO: Add property support - * - * overview.md - * Page Properties: - * Project: [[Project A]] - * File Type: asset - * - * It would take those Project Property and File Type Properties, moving it to Project A/assets. - * - * - * - * Theoretically, projects should then take precedence over tags and names, as they are more specific. - * - * - * The way i see the implementation is adding a new model that will contain the project destination - * and then specify in subdirectories where each matching file goes. - * And it has to check if the file contains the property from the project such as it was mentioned in the overview.md example. - * - */ - - public getPropertiesFromFile(file: TFile): Record { - console.log("Extracted properties: ", properties); - return properties; - } -} - -const propertyUtil = PropertyUtil.getInstance(); -export default propertyUtil; diff --git a/Utils/RuleMatcherUtil.ts b/Utils/RuleMatcherUtil.ts index 209aabd..9bf3b41 100644 --- a/Utils/RuleMatcherUtil.ts +++ b/Utils/RuleMatcherUtil.ts @@ -1,4 +1,5 @@ import type { MovingRule } from "Models/MovingRule"; +import { ProjectRule } from "Models/ProjectRule"; import type { TagCache, TFile } from "obsidian"; class RuleMatcherUtil { diff --git a/main.ts b/main.ts index a836749..a29ebe7 100644 --- a/main.ts +++ b/main.ts @@ -6,6 +6,7 @@ import movingUtil from "Utils/MovingUtil"; import ruleMatcherUtil from "Utils/RuleMatcherUtil"; import timerUtil from "Utils/TimerUtil"; import * as obsidian from "obsidian"; +import projectMatcherUtil from "Utils/ProjectMatcherUtil"; export default class AutoMoverPlugin extends obsidian.Plugin { settings: Settings.AutoMoverSettings; @@ -103,8 +104,9 @@ export default class AutoMoverPlugin extends obsidian.Plugin { * @returns void */ matchAndMoveFile(file: obsidian.TFile): void { - // console.log("Moving file: ", file.path); - if (this.matchAndMoveByName(file)) return; + console.log("Moving file: ", file.path); + if (this.matchAndMoveByProject(file)) return; + else if (this.matchAndMoveByName(file)) return; else this.matchAndMoveByTag(file); } @@ -152,6 +154,44 @@ export default class AutoMoverPlugin extends obsidian.Plugin { return true; } + matchAndMoveByProject(file: obsidian.TFile): boolean { + const cache = this.app.metadataCache.getFileCache(file); + if (cache == null) return false; + if (cache.frontmatter == null) return false; + if (cache.frontmatter.Project == null && cache.frontmatter.project == null) return false; + console.log("Frontmatter found: ", cache.frontmatter); + + const projectName: string = cache.frontmatter.Project || cache.frontmatter.project; + console.log("Project name found: ", projectName); + + const projectRule = projectMatcherUtil.getMatchingProjectRule(projectName, this.settings.projectRules); + if (projectRule == null || projectRule.folder == null) return false; + if (projectRule.rules == null || projectRule.rules.length === 0) return false; + + console.log("Project rule found: ", projectRule); + + const rule = ruleMatcherUtil.getMatchingRuleByName(file, projectRule.rules); + if (rule == null || rule.folder == null) return false; + + console.log("Project rule's moving rule found: ", rule); + + if (ruleMatcherUtil.isRegexGrouped(rule)) { + const matches = ruleMatcherUtil.getGroupMatches(file, rule); + const ruleDesinationPath = ruleMatcherUtil.constructFinalDesinationPath(rule, matches!); + const finalDestinationPath = projectMatcherUtil.constructProjectDestinationPath(projectRule, ruleDesinationPath); + + console.log("Final destination path: ", finalDestinationPath); + + movingUtil.moveFile(file, finalDestinationPath); + } else { + const finalDestinationPath = projectMatcherUtil.constructProjectDestinationPath(projectRule, rule.folder); + + console.log("Final destination path: ", finalDestinationPath); + movingUtil.moveFile(file, finalDestinationPath); + } + return true; + } + async asyncloadSettings() { this.settings = Object.assign({}, Settings.DEFAULT_SETTINGS, await this.loadData()); } diff --git a/manifest.json b/manifest.json index 1222bb5..c2dc0aa 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "auto-mover", "name": "AutoMover", - "version": "1.0.6", + "version": "1.0.7", "minAppVersion": "0.15.0", "description": "Moves files with specified names into the same folder.", "author": "Al0cam", diff --git a/package.json b/package.json index bc002ff..9f7cd94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "AutoMover", - "version": "1.0.6", + "version": "1.0.7", "description": "AutoMover: Moves files with specified names/tags where you want.", "main": "main.js", "scripts": { diff --git a/versions.json b/versions.json index 94fc44d..d0c1a12 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "1.0.6": "0.15.0" + "1.0.7": "0.15.0" }