Skip to content

Commit

Permalink
Merge pull request #237 from FlorianWoelki/add-reordering-for-custom-…
Browse files Browse the repository at this point in the history
…rules

Add reordering functionality for custom rules
  • Loading branch information
FlorianWoelki authored Sep 17, 2023
2 parents d650068 + d5dcb24 commit 3fabdc1
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 18 deletions.
27 changes: 16 additions & 11 deletions src/lib/customRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,19 @@ const doesMatchFileType = (rule: CustomRule, fileType: CustomRuleFileType): bool
const isApplicable = async (plugin: IconFolderPlugin, rule: CustomRule, file: TAbstractFile): Promise<boolean> => {
// Gets the file type based on the specified file path.
const fileType = (await plugin.app.vault.adapter.stat(file.path)).type;
const toMatch = rule.useFilePath ? file.path : file.name;

try {
// Rule is in some sort of regex.
const regex = new RegExp(rule.rule);
if (!file.name.match(regex)) {
if (!toMatch.match(regex)) {
return false;
}

return doesMatchFileType(rule, fileType);
} catch {
// Rule is not in some sort of regex, check for basic string match.
return file.name.includes(rule.rule) && doesMatchFileType(rule, fileType);
return toMatch.includes(rule.rule) && doesMatchFileType(rule, fileType);
}
};

Expand Down Expand Up @@ -70,13 +71,12 @@ const removeFromAllFiles = async (plugin: IconFolderPlugin, rule: CustomRule): P
};

/**
* Really dumb way to sort the custom rules. At the moment, it only sorts the custom rules
* based on the `localCompare` function.
* Gets all the custom rules sorted by their rule property in ascending order.
* @param plugin Instance of IconFolderPlugin.
* @returns An array of sorted custom rules.
*/
const getSortedRules = (plugin: IconFolderPlugin): CustomRule[] => {
return plugin.getSettings().rules.sort((a, b) => a.rule.localeCompare(b.rule));
return plugin.getSettings().rules.sort((a, b) => a.order - b.order);
};

/**
Expand Down Expand Up @@ -114,37 +114,42 @@ const addToAllFiles = async (plugin: IconFolderPlugin, rule: CustomRule): Promis
* @param rule Custom rule that will be used to check if the rule is applicable to the file.
* @param file File or folder that will be used to possibly create the icon for.
* @param container Optional element where the icon will be added if the custom rules matches.
* @returns A promise that resolves to true if the icon was added, false otherwise.
*/
const add = async (
plugin: IconFolderPlugin,
rule: CustomRule,
file: TAbstractFile,
container?: HTMLElement,
): Promise<void> => {
): Promise<boolean> => {
if (container && dom.doesElementHasIconNode(container)) {
return;
return false;
}

// Gets the type of the file.
const fileType = (await plugin.app.vault.adapter.stat(file.path)).type;

const hasIcon = plugin.getIconNameFromPath(file.path);
if (!doesMatchFileType(rule, fileType) || hasIcon) {
return;
return false;
}
const toMatch = rule.useFilePath ? file.path : file.name;
try {
// Rule is in some sort of regex.
const regex = new RegExp(rule.rule);
if (toMatch.match(regex)) {
dom.createIconNode(plugin, file.path, rule.icon, { color: rule.color, container });
return true;
}
} catch {
// Rule is not applicable to a regex format.
if (toMatch.includes(rule.rule)) {
dom.createIconNode(plugin, file.path, rule.icon, { color: rule.color, container });
return true;
}
}

return false;
};

/**
Expand All @@ -154,16 +159,16 @@ const add = async (
* @returns True if the rule exists in the path, false otherwise.
*/
const doesExistInPath = (rule: CustomRule, path: string): boolean => {
const name = rule.useFilePath ? path : path.split('/').pop();
const toMatch = rule.useFilePath ? path : path.split('/').pop();
try {
// Rule is in some sort of regex.
const regex = new RegExp(rule.rule);
if (name.match(regex)) {
if (toMatch.match(regex)) {
return true;
}
} catch {
// Rule is not in some sort of regex, check for basic string match.
return name.includes(rule.rule);
return toMatch.includes(rule.rule);
}

return false;
Expand Down
1 change: 1 addition & 0 deletions src/lib/iconTabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ const add = async (plugin: IconFolderPlugin, file: TFile, options?: AddOptions):
dom.setIconForNode(plugin, rule.icon, iconContainer, rule.color);
// TODO: Refactor to include option to `insertIconToNode` function.
iconContainer.style.margin = null;
break;
}
}

Expand Down
37 changes: 34 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ export default class IconFolderPlugin extends Plugin {
this.getSettings().migrated++;
}

// Migration for new order functionality of custom rules.
if (this.getSettings().migrated === 2) {
// Sorting alphabetically was the default behavior before.
this.getSettings()
.rules.sort((a, b) => a.rule.localeCompare(b.rule))
.forEach((rule, i) => {
rule.order = i;
});
this.getSettings().migrated++;
}

const extraPadding = (this.getSettings() as any).extraPadding as ExtraMarginSettings;
if (extraPadding) {
if (extraPadding.top !== 2 || extraPadding.bottom !== 2 || extraPadding.left !== 2 || extraPadding.right !== 2) {
Expand Down Expand Up @@ -241,7 +252,7 @@ export default class IconFolderPlugin extends Plugin {
// Register rename event for adding icons with custom rules to the DOM and updating
// inheritance when file was moved to another directory.
this.registerEvent(
this.app.vault.on('rename', (file, oldPath) => {
this.app.vault.on('rename', async (file, oldPath) => {
const inheritanceExists = inheritance.doesExistInPath(this, oldPath);
if (inheritanceExists) {
// Apply inheritance to the renamed file.
Expand All @@ -261,14 +272,34 @@ export default class IconFolderPlugin extends Plugin {
});
}
} else {
// Apply custom rules to the renamed file.
customRule.getSortedRules(this).forEach((rule) => {
const sortedRules = customRule.getSortedRules(this);

// Removes possible icons from the renamed file.
sortedRules.forEach((rule) => {
if (customRule.doesExistInPath(rule, oldPath)) {
dom.removeIconInPath(file.path);
}
});

// Adds possible icons to the renamed file.
sortedRules.forEach((rule) => {
if (customRule.doesExistInPath(rule, oldPath)) {
return;
}

customRule.add(this, rule, file, undefined);
});

// Updates icon tabs for the renamed file.
for (const rule of customRule.getSortedRules(this)) {
const applicable = await customRule.isApplicable(this, rule, file);
if (!applicable) {
continue;
}

iconTabs.update(this, file as TFile, rule.icon);
break;
}
}
}),
);
Expand Down
3 changes: 2 additions & 1 deletion src/settings/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface ExtraMarginSettings {
export interface CustomRule {
rule: string;
icon: string;
order: number;
color?: string;
useFilePath?: boolean;
for?: 'everything' | 'files' | 'folders';
Expand All @@ -27,7 +28,7 @@ export interface IconFolderSettings {
}

export const DEFAULT_SETTINGS: IconFolderSettings = {
migrated: 1,
migrated: 2, // Bump up this number for migrations.
iconPacksPath: '.obsidian/plugins/obsidian-icon-folder/icons',
fontSize: 16,
emojiStyle: 'none',
Expand Down
81 changes: 78 additions & 3 deletions src/settings/ui/customIconRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { App, Notice, Setting, TextComponent, ColorComponent, ButtonComponent, M
import IconFolderSetting from './iconFolderSetting';
import IconsPickerModal from '@app/iconsPickerModal';
import IconFolderPlugin from '@app/main';
import { getAllOpenedFiles, removeIconFromIconPack, saveIconToIconPack } from '@app/util';
import { getAllOpenedFiles, getFileItemTitleEl, removeIconFromIconPack, saveIconToIconPack } from '@app/util';
import { CustomRule } from '../data';
import customRule from '@lib/customRule';
import iconTabs from '@lib/iconTabs';
Expand All @@ -27,9 +27,13 @@ export default class CustomIconRuleSetting extends IconFolderSetting {
* @param rule Rule that will be used to update all the icons for all opened files.
* @param remove Whether to remove the icons that are applicable to the rule or not.
*/
private async updateIconTabs(rule: CustomRule, remove: boolean): Promise<void> {
private async updateIconTabs(rule: CustomRule, remove: boolean, cachedPaths: string[] = []): Promise<void> {
if (this.plugin.getSettings().iconInTabsEnabled) {
for (const openedFile of getAllOpenedFiles(this.plugin)) {
if (cachedPaths.includes(openedFile.path)) {
continue;
}

const applicable = await customRule.isApplicable(this.plugin, rule, openedFile);
if (!applicable) {
continue;
Expand Down Expand Up @@ -80,7 +84,12 @@ export default class CustomIconRuleSetting extends IconFolderSetting {
modal.onChooseItem = async (item) => {
const icon = getNormalizedName(typeof item === 'object' ? item.displayName : item);

const rule: CustomRule = { rule: this.textComponent.getValue(), icon, for: 'everything' };
const rule: CustomRule = {
rule: this.textComponent.getValue(),
icon,
for: 'everything',
order: this.plugin.getSettings().rules.length,
};
this.plugin.getSettings().rules = [...this.plugin.getSettings().rules, rule];
await this.plugin.saveIconFolderData();

Expand All @@ -102,6 +111,72 @@ export default class CustomIconRuleSetting extends IconFolderSetting {
// Keeping track of the old rule so that we can get a reference to it for old values.
const oldRule = { ...rule };
const settingRuleEl = new Setting(this.containerEl).setName(rule.rule).setDesc(`Icon: ${rule.icon}`);
const currentOrder = rule.order;

/**
* Re-orders the custom rule based on the value that is passed in.
* @param valueForReorder Number that will be used to determine whether to swap the
* custom rule with the next rule or the previous rule.
*/
const orderCustomRules = async (valueForReorder: number): Promise<void> => {
const otherRule = this.plugin.getSettings().rules[currentOrder + valueForReorder];
// Swap the current rule with the next rule.
otherRule.order = otherRule.order - valueForReorder;
rule.order = currentOrder + valueForReorder;
// Refreshes the DOM.
await customRule.removeFromAllFiles(this.plugin, oldRule);
await this.plugin.saveIconFolderData();

const addedPaths: string[] = [];
for (const fileExplorer of this.plugin.getRegisteredFileExplorers()) {
const files = Object.values(fileExplorer.fileItems);
for (const rule of customRule.getSortedRules(this.plugin)) {
// Removes the icon tabs from all opened files.
this.updateIconTabs(rule, true, addedPaths);
// Adds the icon tabs to all opened files.
this.updateIconTabs(rule, false, addedPaths);

for (const fileItem of files) {
if (addedPaths.includes(fileItem.file.path)) {
continue;
}

const added = await customRule.add(this.plugin, rule, fileItem.file, getFileItemTitleEl(fileItem));
if (added) {
addedPaths.push(fileItem.file.path);
}
}
}
}

this.refreshDisplay();
};

// Add the move down custom rule button to re-order the custom rule.
settingRuleEl.addExtraButton((btn) => {
const isFirstOrder = currentOrder === 0;
btn.setDisabled(isFirstOrder);
btn.extraSettingsEl.style.cursor = isFirstOrder ? 'not-allowed' : 'default';
btn.extraSettingsEl.style.opacity = isFirstOrder ? '50%' : '100%';
btn.setIcon('arrow-up');
btn.setTooltip('Prioritize the custom rule');
btn.onClick(async () => {
await orderCustomRules(-1);
});
});

// Add the move up custom rule button to re-order the custom rule.
settingRuleEl.addExtraButton((btn) => {
const isLastOrder = currentOrder === this.plugin.getSettings().rules.length - 1;
btn.setDisabled(isLastOrder);
btn.extraSettingsEl.style.cursor = isLastOrder ? 'not-allowed' : 'default';
btn.extraSettingsEl.style.opacity = isLastOrder ? '50%' : '100%';
btn.setIcon('arrow-down');
btn.setTooltip('Deprioritize the custom rule');
btn.onClick(async () => {
await orderCustomRules(1);
});
});

// Add the configuration button for configuring where the custom rule gets applied to.
settingRuleEl.addButton((btn) => {
Expand Down

0 comments on commit 3fabdc1

Please sign in to comment.