Merge pull request #4 from al0cam/tags

Tags
This commit is contained in:
Benjamin Filip Šikač 2025-09-27 19:30:05 +02:00 committed by GitHub
commit 0459f5f5cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 430 additions and 277 deletions

20
IO/RuleIO.ts Normal file
View file

@ -0,0 +1,20 @@
class SettingsIO {
private static instance: SettingsIO;
private constructor() {}
public static getInstance(): SettingsIO {
if (!SettingsIO.instance) {
SettingsIO.instance = new SettingsIO();
}
return SettingsIO.instance;
}
// reading from yaml
// reading from json
// reading from regular txt file
}
const settingsIO = SettingsIO.getInstance();
export default settingsIO;

View file

@ -1,6 +1,20 @@
/**
* Regex based rule for moving files
*/
export class MovingRule {
/**
* This filed is used for defining the regex with its group matchers
* This field is used for defining the regex with its group matchers
*
* This can contain the follwoing:
* - Names of files w/w/o extensions
* - Names of folders
* - Extensions of files
* - Regex strings w/w/o groups
* - Any combination of the above
*
* Tag update:
* - The regex can now hold tags as well, which can be used to match files based on their tags.
*
*/
public regex: string;
/**

View file

@ -56,6 +56,13 @@ Other examples could include:
3. 72:30:00 -> triggers every 3 days and 30 minutes (if your Device is online and obsidian runnning for that long)
### Tag rules UI
From patch 1.0.6 onwards, you can move files using tags.
It takes into account the first rule that is a match,
**if it first matches with a FileName rule, it won't check the Tag rules.**
There are no pictures for this one, as it is identical to the previous ruleset.
### Exclusion rules UI
From patch 1.0.2 onwards, you can exclude files and folders from being moved.
The UI that is used for the exclusion rules is the same as the one used for the search criteria.
@ -193,6 +200,7 @@ Thank you!
- [x] Exposing the move files button to the left toolbar
- [x] Exposing the move files to the commands accessible via command palette
- [x] Add import and export of rules
- [x] Add #tag rule support
- [ ] Add a file like .gitignore which contains all the moving rules (i am assuming the list can grow quite big for some people)
- [ ] Auto tagging of moved files with the destination folder name (last folder in the path)
- [ ] Add undo button to the notification popup for the moved files

View file

@ -0,0 +1,74 @@
import { ExclusionRule } from "Models/ExclusionRule";
import type AutoMoverPlugin from "main";
import { Setting } from "obsidian";
export function exclusionSection(
containerEl: HTMLElement,
plugin: AutoMoverPlugin,
display: () => void,
) {
/**
* Header for excluded folders
*/
const exclusionRuleContainer = containerEl.createDiv({
cls: "moving_rules_container",
});
new Setting(exclusionRuleContainer).setName("Exclusion rules").setHeading();
const exclusionList = exclusionRuleContainer.createDiv({
cls: "rule_list",
});
const exclusionHeader = exclusionList.createDiv({
cls: "rule margig_right",
});
exclusionHeader.createEl("p", {
text: "Excluded folders or files (string or regex)",
cls: "rule_title",
});
const addExclusionButton = exclusionHeader.createEl("button", {
text: "+",
cls: "rule_button",
});
addExclusionButton.addEventListener("click", () => {
plugin.settings.exclusionRules.push(new ExclusionRule());
display();
});
/**
* List of excluded folders
*/
for (const exclusion of plugin.settings.exclusionRules) {
const child = exclusionList.createDiv({ cls: "rule" });
child.createEl("input", {
value: exclusion.regex,
cls: "rule_input",
}).onchange = (e) => {
exclusion.regex = (e.target as HTMLInputElement).value;
plugin.settings.exclusionRules.map((ef) =>
ef === exclusion ? exclusion : ef,
);
plugin.saveData(plugin.settings);
};
const duplicateExclusionButton = child.createEl("button", {
text: "⿻",
cls: "rule_button rule_button_duplicate",
});
duplicateExclusionButton.addEventListener("click", () => {
plugin.settings.exclusionRules.push(new ExclusionRule(exclusion.regex));
display();
});
const deleteExclusionButton = child.createEl("button", {
text: "x",
cls: "rule_button rule_button_remove",
});
deleteExclusionButton.addEventListener("click", () => {
plugin.settings.exclusionRules = plugin.settings.exclusionRules.filter(
(r) => r !== exclusion,
);
display();
});
}
}

View file

@ -0,0 +1,81 @@
import AutoMoverPlugin from "main";
import { MovingRule } from "Models/MovingRule";
import { Setting } from "obsidian";
export default function movingRuleSection(
containerEl: HTMLElement,
plugin: AutoMoverPlugin,
display: () => void,
) {
/**
* Header of the rules
*/
const movingRulesContainer = containerEl.createDiv({
cls: "moving_rules_container",
});
new Setting(movingRulesContainer).setName("Moving rules").setHeading();
const ruleList = movingRulesContainer.createDiv({ cls: "rule_list" });
const ruleHeader = ruleList.createDiv({ cls: "rule margig_right" });
ruleHeader.createEl("p", {
text: "Search criteria (string or regex)",
cls: "rule_title",
});
ruleHeader.createEl("p", {
text: "Folder (string that can contain regex groups)",
cls: "rule_title",
});
const addRuleButton = ruleHeader.createEl("button", {
text: "+",
cls: "rule_button",
});
addRuleButton.addEventListener("click", () => {
plugin.settings.movingRules.push(new MovingRule());
display();
});
/**
* List of rules
*/
for (const rule of plugin.settings.movingRules) {
const child = ruleList.createDiv({ cls: "rule" });
child.createEl("input", {
value: rule.regex,
cls: "rule_input",
}).onchange = (e) => {
rule.regex = (e.target as HTMLInputElement).value;
plugin.settings.movingRules.map((r) => (r === rule ? rule : r));
plugin.saveData(plugin.settings);
};
child.createEl("input", {
value: rule.folder,
cls: "rule_input",
}).onchange = (e) => {
rule.folder = (e.target as HTMLInputElement).value;
plugin.settings.movingRules.map((r) => (r === rule ? rule : r));
plugin.saveData(plugin.settings);
};
const duplicateRuleButton = child.createEl("button", {
text: "⿻",
cls: "rule_button rule_button_duplicate",
});
duplicateRuleButton.addEventListener("click", () => {
plugin.settings.movingRules.push(new MovingRule(rule.regex, rule.folder));
display();
});
const deleteRuleButton = child.createEl("button", {
text: "x",
cls: "rule_button rule_button_remove",
});
deleteRuleButton.addEventListener("click", () => {
plugin.settings.movingRules = plugin.settings.movingRules.filter(
(r) => r !== rule,
);
display();
});
}
}

View file

@ -7,6 +7,7 @@ export interface AutoMoverSettings {
// moveOnSave: boolean;
movingRules: MovingRule[];
exclusionRules: ExclusionRule[];
tagRules: MovingRule[];
automaticMoving: boolean;
timer: number | null; // in miliseconds
}
@ -16,6 +17,7 @@ export const DEFAULT_SETTINGS: Partial<AutoMoverSettings> = {
// moveOnSave: true,
movingRules: [],
exclusionRules: [],
tagRules: [],
automaticMoving: false,
timer: null,
};

View file

@ -1,24 +1,25 @@
import settingsIO from "IO/SettingsIO";
import { ExclusionRule } from "Models/ExclusionRule";
import { MovingRule } from "Models/MovingRule";
import timerUtil from "Utils/TimerUtil";
import type AutoMoverPlugin from "main";
import * as obsidian from "obsidian";
import { type App, PluginSettingTab, Setting } from "obsidian";
import { exclusionSection } from "./ExclusionSection";
import movingRuleSection from "./MovingRuleSection";
import { tagSection } from "./TagSection";
export class SettingsTab extends obsidian.PluginSettingTab {
export class SettingsTab extends PluginSettingTab {
plugin: AutoMoverPlugin;
constructor(app: obsidian.App, plugin: AutoMoverPlugin) {
constructor(app: App, plugin: AutoMoverPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
display = () => {
const { containerEl } = this;
containerEl.empty();
new obsidian.Setting(containerEl).setName("Automatic moving").setHeading();
new obsidian.Setting(containerEl)
new Setting(containerEl).setName("Automatic moving").setHeading();
new Setting(containerEl)
.setName("Export/Import of settings")
.setDesc(
`Import will replace the current settings.
@ -37,16 +38,13 @@ export class SettingsTab extends obsidian.PluginSettingTab {
if (importedSettings) {
this.plugin.settings = importedSettings;
await this.plugin.saveData(this.plugin.settings);
this.plugin.app.workspace.trigger(
"AutoMover:automatic-moving-update",
this.plugin.settings,
);
this.plugin.app.workspace.trigger("AutoMover:automatic-moving-update", this.plugin.settings);
this.display();
}
});
});
new obsidian.Setting(containerEl)
new Setting(containerEl)
.setName("Move on open")
.setDesc("Should the file be moved when it is opened?")
.addToggle((cb) =>
@ -56,11 +54,9 @@ export class SettingsTab extends obsidian.PluginSettingTab {
}),
);
new obsidian.Setting(containerEl)
new Setting(containerEl)
.setName("Manually move")
.setDesc(
"Execute the command to go through your notes and move them according to the rules specified below.",
)
.setDesc("Execute the command to go through your notes and move them according to the rules specified below.")
.addButton((button) => {
button.setButtonText("Move files");
button.onClick(async () => {
@ -70,7 +66,7 @@ export class SettingsTab extends obsidian.PluginSettingTab {
const automaticMovingContainer = containerEl.createDiv({});
new obsidian.Setting(automaticMovingContainer)
new 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.
@ -79,17 +75,12 @@ export class SettingsTab extends obsidian.PluginSettingTab {
)
.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();
}),
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
@ -99,26 +90,10 @@ export class SettingsTab extends obsidian.PluginSettingTab {
.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,
);
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
//
// new obsidian.Setting(containerEl)
// .setName("Move on save")
// .setDesc("Should the file be moved when it is saved?")
// .addToggle((cb) =>
// cb.setValue(this.plugin.settings.moveOnSave).onChange(async (value) => {
// this.plugin.settings.moveOnSave = value;
// await this.plugin.saveData(this.plugin.settings);
// }),
// );
// TUTORIAL START
/**
* Instead of using the default .setting-item class I created a custom class to add other styling,
@ -127,7 +102,7 @@ export class SettingsTab extends obsidian.PluginSettingTab {
const tutorialContainer = containerEl.createDiv({
cls: "moving_rules_container",
});
new obsidian.Setting(tutorialContainer).setName("Tutorial").setHeading();
new Setting(tutorialContainer).setName("Tutorial").setHeading();
/**
* Rule description and tutorial
*/
@ -168,152 +143,8 @@ export class SettingsTab extends obsidian.PluginSettingTab {
example2.createSpan({ text: "Scrolls/$1", cls: "rule_title" });
// TUTORIAL END
// MOVING RULES START
/**
* Header of the rules
*/
const movingRulesContainer = containerEl.createDiv({
cls: "moving_rules_container",
});
new obsidian.Setting(movingRulesContainer)
.setName("Moving rules")
.setHeading();
const ruleList = movingRulesContainer.createDiv({ cls: "rule_list" });
const ruleHeader = ruleList.createDiv({ cls: "rule margig_right" });
ruleHeader.createEl("p", {
text: "Search criteria (string or regex)",
cls: "rule_title",
});
ruleHeader.createEl("p", {
text: "Folder (string that can contain regex groups)",
cls: "rule_title",
});
const addRuleButton = ruleHeader.createEl("button", {
text: "+",
cls: "rule_button",
});
addRuleButton.addEventListener("click", () => {
this.plugin.settings.movingRules.push(new MovingRule());
// this is used to rerender the settings tab
this.display();
});
/**
* List of rules
*/
for (const rule of this.plugin.settings.movingRules) {
const child = ruleList.createDiv({ cls: "rule" });
child.createEl("input", {
value: rule.regex,
cls: "rule_input",
}).onchange = (e) => {
rule.regex = (e.target as HTMLInputElement).value;
this.plugin.settings.movingRules.map((r) => (r === rule ? rule : r));
this.plugin.saveData(this.plugin.settings);
};
child.createEl("input", {
value: rule.folder,
cls: "rule_input",
}).onchange = (e) => {
rule.folder = (e.target as HTMLInputElement).value;
this.plugin.settings.movingRules.map((r) => (r === rule ? rule : r));
this.plugin.saveData(this.plugin.settings);
};
const duplicateRuleButton = child.createEl("button", {
text: "⿻",
cls: "rule_button rule_button_duplicate",
});
duplicateRuleButton.addEventListener("click", () => {
this.plugin.settings.movingRules.push(
new MovingRule(rule.regex, rule.folder),
);
this.display();
});
const deleteRuleButton = child.createEl("button", {
text: "x",
cls: "rule_button rule_button_remove",
});
deleteRuleButton.addEventListener("click", () => {
this.plugin.settings.movingRules =
this.plugin.settings.movingRules.filter((r) => r !== rule);
this.display();
});
}
// MOVING RULES END
// EXCLUSION FOLDERS START
/**
* Header for excluded folders
*/
const exclusionRuleContainer = containerEl.createDiv({
cls: "moving_rules_container",
});
new obsidian.Setting(exclusionRuleContainer)
.setName("Exclusion rules")
.setHeading();
const exclusionList = exclusionRuleContainer.createDiv({
cls: "rule_list",
});
const exclusionHeader = exclusionList.createDiv({
cls: "rule margig_right",
});
exclusionHeader.createEl("p", {
text: "Excluded folders or files (string or regex)",
cls: "rule_title",
});
const addExclusionButton = exclusionHeader.createEl("button", {
text: "+",
cls: "rule_button",
});
addExclusionButton.addEventListener("click", () => {
this.plugin.settings.exclusionRules.push(new ExclusionRule());
this.display();
});
/**
* List of excluded folders
*/
for (const exclusion of this.plugin.settings.exclusionRules) {
const child = exclusionList.createDiv({ cls: "rule" });
child.createEl("input", {
value: exclusion.regex,
cls: "rule_input",
}).onchange = (e) => {
exclusion.regex = (e.target as HTMLInputElement).value;
this.plugin.settings.exclusionRules.map((ef) =>
ef === exclusion ? exclusion : ef,
);
this.plugin.saveData(this.plugin.settings);
};
const duplicateExclusionButton = child.createEl("button", {
text: "⿻",
cls: "rule_button rule_button_duplicate",
});
duplicateExclusionButton.addEventListener("click", () => {
this.plugin.settings.exclusionRules.push(
new ExclusionRule(exclusion.regex),
);
this.display();
});
const deleteExclusionButton = child.createEl("button", {
text: "x",
cls: "rule_button rule_button_remove",
});
deleteExclusionButton.addEventListener("click", () => {
this.plugin.settings.exclusionRules =
this.plugin.settings.exclusionRules.filter((r) => r !== exclusion);
this.display();
});
}
// EXCLUSION FOLDERS END
}
movingRuleSection(containerEl, this.plugin, this.display);
tagSection(containerEl, this.plugin, this.display);
exclusionSection(containerEl, this.plugin, this.display);
};
}

80
Settings/TagSection.ts Normal file
View file

@ -0,0 +1,80 @@
import { MovingRule } from "Models/MovingRule";
import * as obsidian from "obsidian";
import type AutoMoverPlugin from "main";
export function tagSection(
containerEl: HTMLElement,
plugin: AutoMoverPlugin,
display: () => void,
) {
/**
* Header for excluded folders
*/
const tagRuleContainer = containerEl.createDiv({
cls: "moving_rules_container",
});
new obsidian.Setting(tagRuleContainer).setName("Tag rules").setHeading();
const tagList = tagRuleContainer.createDiv({
cls: "rule_list",
});
const tagHeader = tagList.createDiv({
cls: "rule margig_right",
});
tagHeader.createEl("p", {
text: "Tag search criteria (string or regex)",
cls: "rule_title",
});
const addTagButton = tagHeader.createEl("button", {
text: "+",
cls: "rule_button",
});
addTagButton.addEventListener("click", () => {
plugin.settings.tagRules.push(new MovingRule());
display();
});
/**
* List of tag rules
*/
for (const rule of plugin.settings.tagRules) {
const child = tagList.createDiv({ cls: "rule" });
child.createEl("input", {
value: rule.regex,
cls: "rule_input",
}).onchange = (e) => {
rule.regex = (e.target as HTMLInputElement).value;
plugin.settings.tagRules.map((r) => (r === rule ? rule : r));
plugin.saveData(plugin.settings);
};
child.createEl("input", {
value: rule.folder,
cls: "rule_input",
}).onchange = (e) => {
rule.folder = (e.target as HTMLInputElement).value;
plugin.settings.tagRules.map((r) => (r === rule ? rule : r));
plugin.saveData(plugin.settings);
};
const duplicateRuleButton = child.createEl("button", {
text: "⿻",
cls: "rule_button rule_button_duplicate",
});
duplicateRuleButton.addEventListener("click", () => {
plugin.settings.tagRules.push(new MovingRule(rule.regex, rule.folder));
display();
});
const deleteRuleButton = child.createEl("button", {
text: "x",
cls: "rule_button rule_button_remove",
});
deleteRuleButton.addEventListener("click", () => {
plugin.settings.tagRules = plugin.settings.tagRules.filter(
(r) => r !== rule,
);
display();
});
}
}

View file

@ -1,5 +1,5 @@
import type { MovingRule } from "Models/MovingRule";
import type { TFile } from "obsidian";
import type { TagCache, TFile } from "obsidian";
class RuleMatcherUtil {
private static instance: RuleMatcherUtil;
@ -21,7 +21,7 @@ class RuleMatcherUtil {
* @param rules
* @returns MovingRule | null
*/
public getMatchingRule(file: TFile, rules: MovingRule[]): MovingRule | null {
public getMatchingRuleByName(file: TFile, rules: MovingRule[]): MovingRule | null {
for (const rule of rules) {
if (rule.regex == null || rule.regex === "") {
console.error("Rule does not have a regex: ", rule);
@ -42,6 +42,37 @@ class RuleMatcherUtil {
return null;
}
/**
* Returns the first rule that matches any of the tags
* If no rule matches, returns null
* @param tags
* @param rules
* @returns MovingRule | null
*/
public getMatchingRuleByTag(tags: TagCache[], rules: MovingRule[]): MovingRule | null {
for (const rule of rules) {
if (rule.regex == null || rule.regex === "") {
console.error("Tag Rule does not have a regex: ", rule);
continue;
}
if (rule.folder == null || rule.folder === "") {
console.error("Tag Rule does not have a destination folder: ", rule);
continue;
}
if (!this.isValidRegex(rule.regex)) {
console.error("Tag Rule has an invalid regex: ", rule);
continue;
}
for (const tag of tags) {
if (tag.tag.match(rule.regex)) {
return rule;
}
}
}
return null;
}
/**
* Returns the regex groups of the file that match the rule
* If no groups match, returns an empty array
@ -50,10 +81,7 @@ class RuleMatcherUtil {
* @param rule
* @returns string[]
*/
public getGroupMatches(
file: TFile,
rule: MovingRule,
): RegExpMatchArray | null {
public getGroupMatches(file: TFile, rule: MovingRule): RegExpMatchArray | null {
const matches = file.name.match(rule.regex);
return matches;
}
@ -84,10 +112,7 @@ class RuleMatcherUtil {
* @param rule
* @param matches
*/
public constructFinalDesinationPath(
rule: MovingRule,
matches: RegExpMatchArray,
): string {
public constructFinalDesinationPath(rule: MovingRule, matches: RegExpMatchArray): string {
let folderPath = rule.folder;
const unnamedGroups = this.getUnnamedGroups(rule.regex);
// it has been asserted before that there is no way this can be null before the call, the rule and file are checked to be valid
@ -105,10 +130,7 @@ class RuleMatcherUtil {
* @returns boolean
*/
public isRegexGrouped(rule: MovingRule): boolean {
return (
this.doesDestinationUseGroups(rule) &&
this.getUnnamedGroups(rule.regex) != null
);
return this.doesDestinationUseGroups(rule) && this.getUnnamedGroups(rule.regex) != null;
}
/**

View file

@ -1,34 +1,35 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
}
"$schema": "https://biomejs.dev/schemas/2.2.2/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 120
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

80
main.ts
View file

@ -17,17 +17,12 @@ export default class AutoMoverPlugin extends obsidian.Plugin {
/**
* Loading settings and creating the settings tab
*/
this.settings = Object.assign(
{},
Settings.DEFAULT_SETTINGS,
await this.loadData(),
);
this.settings = Object.assign({}, Settings.DEFAULT_SETTINGS, await this.loadData());
this.addSettingTab(new SettingsTab(this.app, this));
// negative ifs for easier reading and debugging
if (!this.areMovingTriggersEnabled()) return;
if (!this.areThereRulesToApply()) return;
if (!this.areRulesValid()) return;
this.registerEvent(
this.app.workspace.on("file-open", (file: obsidian.TFile) => {
@ -98,10 +93,7 @@ export default class AutoMoverPlugin extends obsidian.Plugin {
if (!this.areThereExcludedFolders()) return false;
if (!this.areThereRulesToApply()) return false;
return exclusionMatcherUtil.isFilePathExcluded(
file,
this.settings.exclusionRules,
);
return exclusionMatcherUtil.isFilePathExcluded(file, this.settings.exclusionRules);
}
/**
@ -112,29 +104,56 @@ export default class AutoMoverPlugin extends obsidian.Plugin {
*/
matchAndMoveFile(file: obsidian.TFile): void {
// console.log("Moving file: ", file.path);
const rule = ruleMatcherUtil.getMatchingRule(
file,
this.settings.movingRules,
);
if (rule == null || rule.folder == null) return;
if (this.matchAndMoveByName(file)) return;
else this.matchAndMoveByTag(file);
}
/**
* Matches the file by name and moves it to the destination folder
*
* @param file - File to be matched and moved
* @returns true if the file was moved, false otherwise
*/
matchAndMoveByName(file: obsidian.TFile): boolean {
const rule = ruleMatcherUtil.getMatchingRuleByName(file, this.settings.movingRules);
if (rule == null || rule.folder == null) return false;
if (ruleMatcherUtil.isRegexGrouped(rule)) {
const matches = ruleMatcherUtil.getGroupMatches(file, rule);
const finalDestinationPath = ruleMatcherUtil.constructFinalDesinationPath(
rule,
matches!,
);
const finalDestinationPath = ruleMatcherUtil.constructFinalDesinationPath(rule, matches!);
movingUtil.moveFile(file, finalDestinationPath);
} else {
movingUtil.moveFile(file, rule.folder);
}
return true;
}
/**
* Matches the file by tags it contains and moves it to the destination folder
*
* @param file - File to be matched and moved
* @returns true if the file was moved, false otherwise
*/
matchAndMoveByTag(file: obsidian.TFile): boolean {
const tags = this.app.metadataCache.getFileCache(file)?.tags;
if (tags == null || tags.length === 0) return false;
const tagRule = ruleMatcherUtil.getMatchingRuleByTag(tags, this.settings.tagRules);
if (tagRule == null || tagRule.folder == null) return false;
if (ruleMatcherUtil.isRegexGrouped(tagRule)) {
const matches = ruleMatcherUtil.getGroupMatches(file, tagRule);
const finalDestinationPath = ruleMatcherUtil.constructFinalDesinationPath(tagRule, matches!);
movingUtil.moveFile(file, finalDestinationPath);
} else {
movingUtil.moveFile(file, tagRule.folder);
}
return true;
}
async asyncloadSettings() {
this.settings = Object.assign(
{},
Settings.DEFAULT_SETTINGS,
await this.loadData(),
);
this.settings = Object.assign({}, Settings.DEFAULT_SETTINGS, await this.loadData());
}
async onunload() {
@ -156,10 +175,10 @@ export default class AutoMoverPlugin extends obsidian.Plugin {
*/
areThereRulesToApply(): boolean {
return (
this.settings.movingRules.length > 0 &&
this.settings.movingRules.every(
(rule) => rule.regex !== "" && rule.folder !== "",
)
(this.settings.movingRules.length > 0 &&
this.settings.movingRules.every((rule) => rule.regex !== "" && rule.folder !== "")) ||
(this.settings.tagRules.length > 0 &&
this.settings.tagRules.every((rule) => rule.regex !== "" && rule.folder !== ""))
);
}
@ -176,8 +195,9 @@ export default class AutoMoverPlugin extends obsidian.Plugin {
* @returns boolean
*/
areRulesValid(): boolean {
return this.settings.movingRules.every(
(rule) => rule.regex !== "" && rule.folder !== "",
return (
this.settings.movingRules.every((rule) => rule.regex !== "" && rule.folder !== "") &&
this.settings.tagRules.every((rule) => rule.regex !== "" && rule.folder !== "")
);
}

View file

@ -1,7 +1,7 @@
{
"id": "auto-mover",
"name": "AutoMover",
"version": "1.0.5",
"version": "1.0.6",
"minAppVersion": "0.15.0",
"description": "Moves files with specified names into the same folder.",
"author": "Al0cam",

View file

@ -1,7 +1,7 @@
{
"name": "obsidian-sample-plugin",
"version": "1.0.0",
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
"name": "AutoMover",
"version": "1.0.6",
"description": "AutoMover: Moves files with specified names/tags where you want.",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",

View file

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