feat: project rules implemented, readme remaining

This commit is contained in:
Al0cam 2025-11-07 17:59:47 +01:00
parent 7135c5181e
commit 3927556663
9 changed files with 76 additions and 71 deletions

View file

@ -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

View file

@ -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);
};

View file

@ -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;

View file

@ -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<string, string> {
console.log("Extracted properties: ", properties);
return properties;
}
}
const propertyUtil = PropertyUtil.getInstance();
export default propertyUtil;

View file

@ -1,4 +1,5 @@
import type { MovingRule } from "Models/MovingRule";
import { ProjectRule } from "Models/ProjectRule";
import type { TagCache, TFile } from "obsidian";
class RuleMatcherUtil {

44
main.ts
View file

@ -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());
}

View file

@ -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",

View file

@ -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": {

View file

@ -1,3 +1,3 @@
{
"1.0.6": "0.15.0"
"1.0.7": "0.15.0"
}