Add file icons (#155)

* feat: add file icons setting

* feat: render file icons
This commit is contained in:
DecafDev 2024-07-06 14:57:47 -06:00 committed by GitHub
parent 61f7989309
commit ebfd815630
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1009 additions and 300 deletions

View file

@ -51,6 +51,8 @@ Features are what the software should do.
| Left/right buttons to allow a user to scroll between groups | #accessibility |
| Duplicate a filter rule | #filter |
| Random sort | #filter |
| Add file icons | #filter |
| Add file icon setting | #setting |
## Event callbacks

View file

@ -1,7 +1,7 @@
{
"id": "vault-explorer",
"name": "Vault Explorer",
"version": "1.20.0",
"version": "1.21.0",
"minAppVersion": "1.4.13",
"description": "Explore your vault in visual format",
"author": "DecafDev",

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-vault-explorer",
"version": "1.20.0",
"version": "1.21.0",
"description": "Explore your vault in visual format",
"main": "main.js",
"scripts": {

View file

@ -19,15 +19,15 @@ export const DEFAULT_SETTINGS: VaultExplorerPluginSettings = {
filters: {
search: {
isEnabled: true,
value: ""
value: "",
},
favorites: {
isEnabled: true,
value: false
value: false,
},
timestamp: {
isEnabled: true,
value: "all"
value: "all",
},
sort: {
isEnabled: true,
@ -36,8 +36,8 @@ export const DEFAULT_SETTINGS: VaultExplorerPluginSettings = {
custom: {
isEnabled: true,
selectedGroupId: "",
groups: []
}
groups: [],
},
},
views: {
dashboard: {
@ -60,13 +60,14 @@ export const DEFAULT_SETTINGS: VaultExplorerPluginSettings = {
},
related: {
isEnabled: false,
}
},
},
currentView: TExplorerView.GRID,
titleWrapping: "normal",
enableClockUpdates: true,
enableFileIcons: false,
enableScrollButtons: true,
pageSize: 50,
viewOrder: [TExplorerView.GRID, TExplorerView.LIST, TExplorerView.FEED],
pluginVersion: null
}
pluginVersion: null,
};

View file

@ -1,3 +1,21 @@
export type PluginEvent = "file-rename" | "file-create" | "file-delete" | "file-modify" | "metadata-change" | "properties-filter-update" | "folder-rename" | "folder-delete" | "folder-create" | "page-size-setting-change" | "title-wrapping-setting-change" | "property-setting-change" | "device-registration-change" | "clock-updates-setting-change" | "filter-toggle-setting-change" | "scroll-buttons-setting-change" | "view-toggle-setting-change";
export type PluginEvent =
| "file-rename"
| "file-create"
| "file-delete"
| "file-modify"
| "metadata-change"
| "properties-filter-update"
| "folder-rename"
| "folder-delete"
| "folder-create"
| "page-size-setting-change"
| "title-wrapping-setting-change"
| "property-setting-change"
| "device-registration-change"
| "clock-updates-setting-change"
| "filter-toggle-setting-change"
| "scroll-buttons-setting-change"
| "view-toggle-setting-change"
| "file-icons-setting-change";
export type EventCallback = (...data: unknown[]) => void;

View file

@ -1,18 +1,22 @@
import { Plugin, TAbstractFile, TFile, TFolder } from 'obsidian';
import { Plugin, TAbstractFile, TFile, TFolder } from "obsidian";
import VaultExplorerView from './obsidian/vault-explorer-view';
import VaultExplorerSettingsTab from './obsidian/vault-explorer-settings-tab';
import VaultExplorerView from "./obsidian/vault-explorer-view";
import VaultExplorerSettingsTab from "./obsidian/vault-explorer-settings-tab";
import { VaultExplorerPluginSettings } from './types';
import { DEFAULT_SETTINGS, HOVER_LINK_SOURCE_ID, VAULT_EXPLORER_VIEW } from './constants';
import _ from 'lodash';
import EventManager from './event/event-manager';
import { preformMigrations } from './migrations';
import Logger from 'js-logger';
import { formatMessageForLogger, stringToLogLevel } from './logger';
import { moveFocus } from './focus-utils';
import { loadDeviceId } from './svelte/shared/services/device-id-utils';
import License from './svelte/shared/services/license';
import { VaultExplorerPluginSettings } from "./types";
import {
DEFAULT_SETTINGS,
HOVER_LINK_SOURCE_ID,
VAULT_EXPLORER_VIEW,
} from "./constants";
import _ from "lodash";
import EventManager from "./event/event-manager";
import { preformMigrations } from "./migrations";
import Logger from "js-logger";
import { formatMessageForLogger, stringToLogLevel } from "./logger";
import { moveFocus } from "./focus-utils";
import { loadDeviceId } from "./svelte/shared/services/device-id-utils";
import License from "./svelte/shared/services/license";
export default class VaultExplorerPlugin extends Plugin {
settings: VaultExplorerPluginSettings = DEFAULT_SETTINGS;
@ -40,7 +44,10 @@ export default class VaultExplorerPlugin extends Plugin {
});
this.registerEvents();
this.registerHoverLinkSource(HOVER_LINK_SOURCE_ID, { display: this.manifest.name, defaultMod: true });
this.registerHoverLinkSource(HOVER_LINK_SOURCE_ID, {
display: this.manifest.name,
defaultMod: true,
});
this.addSettingTab(new VaultExplorerSettingsTab(this.app, this));
this.app.workspace.onLayoutReady(() => {
@ -49,60 +56,79 @@ export default class VaultExplorerPlugin extends Plugin {
await loadDeviceId();
await License.getInstance().verifyLicense();
}
private registerEvents() {
//Callback if the file is renamed or moved
//This callback is already debounced by Obsidian
this.registerEvent(this.app.vault.on("rename", (file: TAbstractFile, oldPath: string) => {
if (file instanceof TFolder) {
EventManager.getInstance().emit("folder-rename", oldPath, file);
} else if (file instanceof TFile) {
EventManager.getInstance().emit("file-rename", oldPath, file);
}
}));
this.registerEvent(
this.app.vault.on(
"rename",
(file: TAbstractFile, oldPath: string) => {
if (file instanceof TFolder) {
EventManager.getInstance().emit(
"folder-rename",
oldPath,
file
);
} else if (file instanceof TFile) {
EventManager.getInstance().emit(
"file-rename",
oldPath,
file
);
}
}
)
);
//Callback if a file is deleted
//This callback is already debounced by Obsidian
this.registerEvent(this.app.vault.on("delete", (file: TAbstractFile) => {
if (file instanceof TFolder) {
EventManager.getInstance().emit("folder-delete", file.path);
} else
if (file instanceof TFile) {
this.registerEvent(
this.app.vault.on("delete", (file: TAbstractFile) => {
if (file instanceof TFolder) {
EventManager.getInstance().emit("folder-delete", file.path);
} else if (file instanceof TFile) {
EventManager.getInstance().emit("file-delete", file.path);
}
}));
})
);
//Callback if a file is created
//This callback is already debounced by Obsidian
this.registerEvent(this.app.vault.on("create", (file: TAbstractFile) => {
//For some reason Obsidian will call this event for every file in the vault when the plugin is loaded
//We need to ignore these events
if (!this.layoutReady) return;
this.registerEvent(
this.app.vault.on("create", (file: TAbstractFile) => {
//For some reason Obsidian will call this event for every file in the vault when the plugin is loaded
//We need to ignore these events
if (!this.layoutReady) return;
if (file instanceof TFolder) {
EventManager.getInstance().emit("folder-create", file);
} else if (file instanceof TFile) {
EventManager.getInstance().emit("file-create", file);
}
}));
if (file instanceof TFolder) {
EventManager.getInstance().emit("folder-create", file);
} else if (file instanceof TFile) {
EventManager.getInstance().emit("file-create", file);
}
})
);
//Callback if a file is modified
//This callback is already debounced by Obsidian
this.registerEvent(this.app.vault.on("modify", (file: TAbstractFile) => {
if (file instanceof TFile) {
if (file.extension !== "md") return;
EventManager.getInstance().emit("file-modify", file);
}
}));
this.registerEvent(
this.app.vault.on("modify", (file: TAbstractFile) => {
if (file instanceof TFile) {
if (file.extension !== "md") return;
EventManager.getInstance().emit("file-modify", file);
}
})
);
//Callback if the frontmatter is changed
//This callback is already debounced by Obsidian
this.registerEvent(this.app.metadataCache.on("changed", (file) => {
if (file.extension !== "md") return;
EventManager.getInstance().emit("metadata-change", file);
}));
this.registerEvent(
this.app.metadataCache.on("changed", (file) => {
if (file.extension !== "md") return;
EventManager.getInstance().emit("metadata-change", file);
})
);
this.registerDomEvent(document, "keydown", (event) => {
if (event.key === "ArrowLeft") {
@ -111,21 +137,20 @@ export default class VaultExplorerPlugin extends Plugin {
moveFocus("next");
}
});
}
onunload() {
}
onunload() {}
async loadSettings() {
const loadedData: Record<string, unknown> | null = await this.loadData();
const loadedData: Record<string, unknown> | null =
await this.loadData();
let currentData: Record<string, unknown> = {};
if (loadedData !== null) {
//This will be undefined if the settings are from a version before 0.3.0
const loadedVersion = loadedData["pluginVersion"] as string ?? null;
const loadedVersion =
(loadedData["pluginVersion"] as string) ?? null;
if (loadedVersion !== null) {
const newData = preformMigrations(loadedVersion, loadedData);
currentData = newData;
@ -140,8 +165,19 @@ export default class VaultExplorerPlugin extends Plugin {
}
async saveSettings() {
Logger.trace({ fileName: "main.ts", functionName: "saveSettings", message: "called" });
Logger.debug({ fileName: "main.ts", functionName: "saveSettings", message: "saving settings" }, this.settings);
Logger.trace({
fileName: "main.ts",
functionName: "saveSettings",
message: "called",
});
Logger.debug(
{
fileName: "main.ts",
functionName: "saveSettings",
message: "saving settings",
},
this.settings
);
await this.saveData(this.settings);
}

View file

@ -13,6 +13,7 @@ import Migrate_1_17_0 from "./migrate_1_17_0";
import Migrate_1_13_0 from "./migrate_1_13_0";
import Migrate_1_10_0 from "./migrate_1_10_0";
import { isVersionLessThan } from "src/utils";
import Migrate_1_21_0 from "./migrate_1_21_0";
const migrations: TMigration[] = [
{
@ -80,9 +81,17 @@ const migrations: TMigration[] = [
to: "1.17.0",
migrate: Migrate_1_17_0,
},
{
from: "1.20.0",
to: "1.21.0",
migrate: Migrate_1_21_0,
},
];
export const preformMigrations = (settingsVersion: string, data: Record<string, unknown>) => {
export const preformMigrations = (
settingsVersion: string,
data: Record<string, unknown>
) => {
let updatedData = structuredClone(data);
for (const migration of migrations) {
@ -96,4 +105,4 @@ export const preformMigrations = (settingsVersion: string, data: Record<string,
}
return updatedData;
}
};

View file

@ -1,40 +1,42 @@
import { TExplorerView, VaultExplorerPluginSettings } from "src/types";
import { TExplorerView } from "src/types";
import MigrationInterface from "./migration_interface";
import { VaultExplorerPluginSettings_1_16_0 } from "src/types/types-1.16.0";
import { VaultExplorerPluginSettings_1_20_0 } from "src/types/types-1.20.0";
export default class Migrate_1_17_0 implements MigrationInterface {
migrate(data: Record<string, unknown>) {
const typedData = (data as unknown) as VaultExplorerPluginSettings_1_16_0;
const newData: VaultExplorerPluginSettings = {
const typedData = data as unknown as VaultExplorerPluginSettings_1_16_0;
const newData: VaultExplorerPluginSettings_1_20_0 = {
...typedData,
views: {
dashboard: {
isEnabled: false
isEnabled: false,
},
grid: {
isEnabled: true
isEnabled: true,
},
list: {
isEnabled: true
isEnabled: true,
},
table: {
isEnabled: false
isEnabled: false,
},
feed: {
isEnabled: true
isEnabled: true,
},
recommended: {
isEnabled: false
isEnabled: false,
},
related: {
isEnabled: false
}
isEnabled: false,
},
},
viewOrder: typedData.views.order as unknown as TExplorerView[],
enableClockUpdates: typedData.views.enableClockUpdates,
currentView: typedData.views.currentView as unknown as TExplorerView,
titleWrapping: typedData.views.titleWrapping
}
currentView: typedData.views
.currentView as unknown as TExplorerView,
titleWrapping: typedData.views.titleWrapping,
};
delete (newData as any).views.order;
delete (newData as any).views.currentView;
delete (newData as any).views.enableClockUpdates;

View file

@ -0,0 +1,14 @@
import { VaultExplorerPluginSettings } from "src/types";
import MigrationInterface from "./migration_interface";
import { VaultExplorerPluginSettings_1_20_0 } from "src/types/types-1.20.0";
export default class Migrate_1_21_0 implements MigrationInterface {
migrate(data: Record<string, unknown>) {
const typedData = data as unknown as VaultExplorerPluginSettings_1_20_0;
const newData: VaultExplorerPluginSettings = {
...typedData,
enableFileIcons: true,
};
return newData as unknown as Record<string, unknown>;
}
}

View file

@ -1,7 +1,17 @@
import { App, PluginSettingTab, Setting } from "obsidian";
import VaultExplorerPlugin from "src/main";
import { getDropdownOptionsForProperties, getObsidianPropertiesByType } from "./utils";
import { LOG_LEVEL_DEBUG, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_OFF, LOG_LEVEL_TRACE, LOG_LEVEL_WARN } from "src/logger/constants";
import {
getDropdownOptionsForProperties,
getObsidianPropertiesByType,
} from "./utils";
import {
LOG_LEVEL_DEBUG,
LOG_LEVEL_ERROR,
LOG_LEVEL_INFO,
LOG_LEVEL_OFF,
LOG_LEVEL_TRACE,
LOG_LEVEL_WARN,
} from "src/logger/constants";
import Logger from "js-logger";
import { stringToLogLevel } from "src/logger";
import { TExplorerView, WordBreak } from "src/types";
@ -25,160 +35,214 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
const textProperties = getObsidianPropertiesByType(this.app, "text");
const dateProperties = getObsidianPropertiesByType(this.app, "date");
const dateTimeProperties = getObsidianPropertiesByType(this.app, "datetime");
const checkboxProperties = getObsidianPropertiesByType(this.app, "checkbox");
const dateTimeProperties = getObsidianPropertiesByType(
this.app,
"datetime"
);
const checkboxProperties = getObsidianPropertiesByType(
this.app,
"checkbox"
);
new Setting(containerEl).setName("General").setHeading();
new Setting(containerEl).setName("Page size").setDesc("The number of items to display per page.").addDropdown(dropdown => dropdown
.addOptions({
"10": "10",
"25": "25",
"50": "50",
"100": "100",
"250": "250",
"500": "500",
})
.setValue(this.plugin.settings.pageSize.toString())
.onChange(async (value) => {
this.plugin.settings.pageSize = parseInt(value);
await this.plugin.saveSettings();
EventManager.getInstance().emit("page-size-setting-change");
}));
new Setting(containerEl)
.setName("Title wrapping")
.setDesc(
"Sets the wrapping style for the title."
)
.setDesc("Set the wrapping style for the title.")
.addDropdown((cb) => {
cb.addOptions({
"normal": "Normal",
normal: "Normal",
"break-word": "Break Word",
})
});
cb.setValue(this.plugin.settings.titleWrapping).onChange(
async (value) => {
this.plugin.settings.titleWrapping = value as WordBreak;
await this.plugin.saveSettings();
EventManager.getInstance().emit("title-wrapping-setting-change");
EventManager.getInstance().emit(
"title-wrapping-setting-change"
);
}
);
});
new Setting(containerEl)
.setName("Enable scroll buttons")
.setDesc("When enabled, scroll buttons will be displayed for scrollable content.")
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enableScrollButtons)
.onChange(async (value) => {
this.plugin.settings.enableScrollButtons = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("scroll-buttons-setting-change");
}));
.setName("File icons")
.setDesc("Display an icon next to the file name.")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.enableFileIcons)
.onChange(async (value) => {
this.plugin.settings.enableFileIcons = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"file-icons-setting-change"
);
})
);
new Setting(containerEl)
.setName("Scroll buttons")
.setDesc("Display scroll buttons for scrollable content.")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.enableScrollButtons)
.onChange(async (value) => {
this.plugin.settings.enableScrollButtons = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"scroll-buttons-setting-change"
);
})
);
new Setting(containerEl)
.setName("Page size")
.setDesc("Number of items to display per page.")
.addDropdown((dropdown) =>
dropdown
.addOptions({
"10": "10",
"25": "25",
"50": "50",
"100": "100",
"250": "250",
"500": "500",
})
.setValue(this.plugin.settings.pageSize.toString())
.onChange(async (value) => {
this.plugin.settings.pageSize = parseInt(value);
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"page-size-setting-change"
);
})
);
new Setting(containerEl).setName("Filters").setHeading();
new Setting(containerEl)
.setName("Search filter")
.addToggle(toggle => toggle
new Setting(containerEl).setName("Search filter").addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.filters.search.isEnabled)
.onChange(async (value) => {
this.plugin.settings.filters.search.isEnabled = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("filter-toggle-setting-change");
}));
EventManager.getInstance().emit(
"filter-toggle-setting-change"
);
})
);
new Setting(containerEl)
.setName("Favorites filter")
.addToggle(toggle => toggle
.setValue(this.plugin.settings.filters.favorites.isEnabled)
.onChange(async (value) => {
this.plugin.settings.filters.favorites.isEnabled = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("filter-toggle-setting-change");
}));
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.filters.favorites.isEnabled)
.onChange(async (value) => {
this.plugin.settings.filters.favorites.isEnabled =
value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"filter-toggle-setting-change"
);
})
);
new Setting(containerEl)
.setName("Timestamp filter")
.addToggle(toggle => toggle
.setValue(this.plugin.settings.filters.timestamp.isEnabled)
.onChange(async (value) => {
this.plugin.settings.filters.timestamp.isEnabled = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("filter-toggle-setting-change");
}));
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.filters.timestamp.isEnabled)
.onChange(async (value) => {
this.plugin.settings.filters.timestamp.isEnabled =
value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"filter-toggle-setting-change"
);
})
);
new Setting(containerEl)
.setName("Sort filter")
.addToggle(toggle => toggle
new Setting(containerEl).setName("Sort filter").addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.filters.sort.isEnabled)
.onChange(async (value) => {
this.plugin.settings.filters.sort.isEnabled = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("filter-toggle-setting-change");
}));
EventManager.getInstance().emit(
"filter-toggle-setting-change"
);
})
);
new Setting(containerEl)
.setName("Custom filter")
.addToggle(toggle => toggle
new Setting(containerEl).setName("Custom filter").addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.filters.custom.isEnabled)
.onChange(async (value) => {
this.plugin.settings.filters.custom.isEnabled = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("filter-toggle-setting-change");
}));
EventManager.getInstance().emit(
"filter-toggle-setting-change"
);
})
);
new Setting(containerEl).setName("Views").setHeading();
new Setting(containerEl)
.setName("Dashboard view")
.addToggle(toggle => toggle
new Setting(containerEl).setName("Dashboard view").addToggle((toggle) =>
toggle
.setDisabled(true) //TODO - Implement dashboard view
.setValue(this.plugin.settings.views.dashboard.isEnabled)
.onChange(async (value) => {
this.plugin.settings.views.dashboard.isEnabled = value;
this.updateViewOrder(TExplorerView.DASHBOARD, value);
await this.plugin.saveSettings();
EventManager.getInstance().emit("view-toggle-setting-change");
}));
EventManager.getInstance().emit(
"view-toggle-setting-change"
);
})
);
new Setting(containerEl)
.setName("Grid view")
.addToggle(toggle => toggle
new Setting(containerEl).setName("Grid view").addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.views.grid.isEnabled)
.onChange(async (value) => {
this.plugin.settings.views.grid.isEnabled = value;
this.updateViewOrder(TExplorerView.GRID, value);
await this.plugin.saveSettings();
EventManager.getInstance().emit("view-toggle-setting-change");
}));
EventManager.getInstance().emit(
"view-toggle-setting-change"
);
})
);
new Setting(containerEl)
.setName("List view")
.addToggle(toggle => toggle
new Setting(containerEl).setName("List view").addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.views.list.isEnabled)
.onChange(async (value) => {
this.plugin.settings.views.list.isEnabled = value;
this.updateViewOrder(TExplorerView.LIST, value);
await this.plugin.saveSettings();
EventManager.getInstance().emit("view-toggle-setting-change");
}));
EventManager.getInstance().emit(
"view-toggle-setting-change"
);
})
);
new Setting(containerEl)
.setName("Feed view")
.addToggle(toggle => toggle
new Setting(containerEl).setName("Feed view").addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.views.feed.isEnabled)
.onChange(async (value) => {
this.plugin.settings.views.feed.isEnabled = value;
this.updateViewOrder(TExplorerView.FEED, value);
await this.plugin.saveSettings();
EventManager.getInstance().emit("view-toggle-setting-change");
}));
EventManager.getInstance().emit(
"view-toggle-setting-change"
);
})
);
new Setting(containerEl)
.setName("Table view")
.addToggle(toggle => toggle
new Setting(containerEl).setName("Table view").addToggle((toggle) =>
toggle
.setDisabled(true) //TODO implement
.setTooltip("This view is not yet implemented.")
.setValue(this.plugin.settings.views.table.isEnabled)
@ -186,26 +250,32 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
this.plugin.settings.views.table.isEnabled = value;
this.updateViewOrder(TExplorerView.TABLE, value);
await this.plugin.saveSettings();
EventManager.getInstance().emit("view-toggle-setting-change");
}));
EventManager.getInstance().emit(
"view-toggle-setting-change"
);
})
);
new Setting(containerEl)
.setName("Recommended view")
.addToggle(toggle => toggle
.setValue(this.plugin.settings.views.recommended.isEnabled)
.setDisabled(true) //TODO implement
.setTooltip("This view is not yet implemented.")
.onChange(async (value) => {
this.plugin.settings.views.recommended.isEnabled = value;
this.updateViewOrder(TExplorerView.RECOMMENDED, value);
await this.plugin.saveSettings();
EventManager.getInstance().emit("view-toggle-setting-change");
}));
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.views.recommended.isEnabled)
.setDisabled(true) //TODO implement
.setTooltip("This view is not yet implemented.")
.onChange(async (value) => {
this.plugin.settings.views.recommended.isEnabled =
value;
this.updateViewOrder(TExplorerView.RECOMMENDED, value);
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"view-toggle-setting-change"
);
})
);
new Setting(containerEl)
.setName("Related view")
.addToggle(toggle => toggle
new Setting(containerEl).setName("Related view").addToggle((toggle) =>
toggle
.setDisabled(true) //TODO implement
.setTooltip("This view is not yet implemented.")
.setValue(this.plugin.settings.views.related.isEnabled)
@ -213,119 +283,178 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
this.plugin.settings.views.related.isEnabled = value;
this.updateViewOrder(TExplorerView.RELATED, value);
await this.plugin.saveSettings();
EventManager.getInstance().emit("view-toggle-setting-change");
}));
EventManager.getInstance().emit(
"view-toggle-setting-change"
);
})
);
new Setting(containerEl).setName("Built-in properties").setHeading();
new Setting(containerEl)
.setName('Favorite property')
.setDesc('The property used to mark a note as a favorite. This must be a checkbox property.')
.addDropdown(dropdown => dropdown.addOptions(getDropdownOptionsForProperties(checkboxProperties))
.setValue(this.plugin.settings.properties.favorite)
.onChange(async (value) => {
this.plugin.settings.properties.favorite = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("property-setting-change");
}));
.setName("Favorite property")
.setDesc(
"Property used to mark a note as a favorite. This must be a checkbox property."
)
.addDropdown((dropdown) =>
dropdown
.addOptions(
getDropdownOptionsForProperties(checkboxProperties)
)
.setValue(this.plugin.settings.properties.favorite)
.onChange(async (value) => {
this.plugin.settings.properties.favorite = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"property-setting-change"
);
})
);
new Setting(containerEl)
.setName('URL property')
.setDesc('The property used to store the URL of the content. This must be a text property.')
.addDropdown(dropdown => dropdown.addOptions(getDropdownOptionsForProperties(textProperties))
.setValue(this.plugin.settings.properties.url)
.onChange(async (value) => {
this.plugin.settings.properties.url = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("property-setting-change");
}));
.setName("URL property")
.setDesc(
"Property used to store the URL of the content. This must be a text property."
)
.addDropdown((dropdown) =>
dropdown
.addOptions(getDropdownOptionsForProperties(textProperties))
.setValue(this.plugin.settings.properties.url)
.onChange(async (value) => {
this.plugin.settings.properties.url = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"property-setting-change"
);
})
);
const creationDateDesc = new DocumentFragment();
creationDateDesc.createDiv({
text: "The property containing the creation date. This must be a date or datetime property.",
text: "Property containing the creation date. This must be a date or datetime property.",
});
creationDateDesc.createDiv({
text: "If set to 'Select a property', the file's created at date will be used.",
});
new Setting(containerEl)
.setName("Creation date property")
.setDesc(creationDateDesc)
.addDropdown(dropdown => dropdown.addOptions(getDropdownOptionsForProperties([...dateProperties, ...dateTimeProperties]))
.setValue(this.plugin.settings.properties.createdDate)
.onChange(async (value) => {
this.plugin.settings.properties.createdDate = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("property-setting-change");
}));
.addDropdown((dropdown) =>
dropdown
.addOptions(
getDropdownOptionsForProperties([
...dateProperties,
...dateTimeProperties,
])
)
.setValue(this.plugin.settings.properties.createdDate)
.onChange(async (value) => {
this.plugin.settings.properties.createdDate = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"property-setting-change"
);
})
);
const modificationDateDesc = new DocumentFragment();
modificationDateDesc.createDiv({
text: "The property containing the modification date. This must be a date or datetime property.",
text: "Property containing the modification date. This must be a date or datetime property.",
});
modificationDateDesc.createDiv({
text: "If set to 'Select a property', the file's modified at date will be used.",
});
new Setting(containerEl)
.setName('Modification date property')
.setName("Modification date property")
.setDesc(modificationDateDesc)
.addDropdown(dropdown => dropdown.addOptions(getDropdownOptionsForProperties([...dateProperties, ...dateTimeProperties]))
.setValue(this.plugin.settings.properties.modifiedDate)
.onChange(async (value) => {
this.plugin.settings.properties.modifiedDate = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("property-setting-change");
}));
.addDropdown((dropdown) =>
dropdown
.addOptions(
getDropdownOptionsForProperties([
...dateProperties,
...dateTimeProperties,
])
)
.setValue(this.plugin.settings.properties.modifiedDate)
.onChange(async (value) => {
this.plugin.settings.properties.modifiedDate = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"property-setting-change"
);
})
);
new Setting(containerEl).setName("Custom properties").setHeading();
new Setting(containerEl)
.setName('Custom property 1')
.setDesc('The first custom property. This must be a text property.')
.addDropdown(dropdown => dropdown.addOptions(getDropdownOptionsForProperties(textProperties))
.setValue(this.plugin.settings.properties.custom1)
.onChange(async (value) => {
this.plugin.settings.properties.custom1 = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("property-setting-change");
}));
.setName("Custom property 1")
.setDesc("First custom property. This must be a text property.")
.addDropdown((dropdown) =>
dropdown
.addOptions(getDropdownOptionsForProperties(textProperties))
.setValue(this.plugin.settings.properties.custom1)
.onChange(async (value) => {
this.plugin.settings.properties.custom1 = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"property-setting-change"
);
})
);
new Setting(containerEl)
.setName('Custom property 2')
.setDesc('The second custom property. This must be a text property.')
.addDropdown(dropdown => dropdown.addOptions(getDropdownOptionsForProperties(textProperties))
.setValue(this.plugin.settings.properties.custom2)
.onChange(async (value) => {
this.plugin.settings.properties.custom2 = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("property-setting-change");
}));
.setName("Custom property 2")
.setDesc("Second custom property. This must be a text property.")
.addDropdown((dropdown) =>
dropdown
.addOptions(getDropdownOptionsForProperties(textProperties))
.setValue(this.plugin.settings.properties.custom2)
.onChange(async (value) => {
this.plugin.settings.properties.custom2 = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"property-setting-change"
);
})
);
new Setting(containerEl)
.setName('Custom property 3')
.setDesc('The third custom property. This must be a text property.')
.addDropdown(dropdown => dropdown.addOptions(getDropdownOptionsForProperties(textProperties))
.setValue(this.plugin.settings.properties.custom3)
.onChange(async (value) => {
this.plugin.settings.properties.custom3 = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("property-setting-change");
}));
.setName("Custom property 3")
.setDesc("Third custom property. This must be a text property.")
.addDropdown((dropdown) =>
dropdown
.addOptions(getDropdownOptionsForProperties(textProperties))
.setValue(this.plugin.settings.properties.custom3)
.onChange(async (value) => {
this.plugin.settings.properties.custom3 = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"property-setting-change"
);
})
);
new Setting(containerEl).setName("Updates").setHeading();
new Setting(containerEl)
.setName("Enable clock updates")
.setDesc("When enabled, time values will update every minute, refreshing the Vault Explorer view. When disabled, time values will only update when the view is first opened.")
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enableClockUpdates)
.onChange(async (value) => {
this.plugin.settings.enableClockUpdates = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit("clock-updates-setting-change");
}));
.setDesc(
"When enabled, time values will update every minute, refreshing the Vault Explorer view. When disabled, time values will only update when the view is first opened."
)
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.enableClockUpdates)
.onChange(async (value) => {
this.plugin.settings.enableClockUpdates = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
"clock-updates-setting-change"
);
})
);
new Setting(containerEl).setName("Premium").setHeading();
@ -337,7 +466,7 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
new Setting(containerEl)
.setName("Log level")
.setDesc(
"Sets the log level. Please use trace to see all log messages."
"Set the log level. Please use trace to see all log messages."
)
.addDropdown((cb) => {
cb.addOptions({
@ -346,8 +475,8 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
[LOG_LEVEL_WARN]: "Warn",
[LOG_LEVEL_INFO]: "Info",
[LOG_LEVEL_DEBUG]: "Debug",
[LOG_LEVEL_TRACE]: "Trace"
})
[LOG_LEVEL_TRACE]: "Trace",
});
cb.setValue(this.plugin.settings.logLevel).onChange(
async (value) => {
this.plugin.settings.logLevel = value;
@ -370,7 +499,9 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
if (this.plugin.settings.currentView == null)
this.plugin.settings.currentView = view;
} else {
const filtered = this.plugin.settings.viewOrder.filter(v => v !== view);
const filtered = this.plugin.settings.viewOrder.filter(
(v) => v !== view
);
this.plugin.settings.viewOrder = filtered;
//If the user turned off the current view, set the current view to the first view

View file

@ -10,19 +10,42 @@
import Stack from "src/svelte/shared/components/stack.svelte";
import Tag from "src/svelte/shared/components/tag.svelte";
import { removeFrontmatterBlock } from "../services/utils/frontmatter-utils";
import Icon from "src/svelte/shared/components/icon.svelte";
import { getIconIdForFile } from "../services/utils/file-icon-utils";
export let name: string;
export let displayName: string;
export let baseName: string;
export let extension: string;
export let path: string;
export let tags: string[] | null;
export let createdMillis: number;
export let content: string | null;
let wordBreak: WordBreak = "normal";
let enableFileIcons = false;
let plugin: VaultExplorerPlugin;
store.plugin.subscribe((value) => {
plugin = value;
wordBreak = plugin.settings.titleWrapping;
enableFileIcons = plugin.settings.enableFileIcons;
});
onMount(() => {
function handleFileIconsChange() {
enableFileIcons = plugin.settings.enableFileIcons;
}
EventManager.getInstance().on(
"file-icons-setting-change",
handleFileIconsChange,
);
return () => {
EventManager.getInstance().off(
"file-icons-setting-change",
handleFileIconsChange,
);
};
});
onMount(() => {
@ -93,7 +116,12 @@
});
}}
>
{name}
<Stack spacing="xs">
{#if enableFileIcons}
<Icon iconId={getIconIdForFile(baseName, extension)} />
{/if}
<span>{displayName}</span>
</Stack>
</div>
{#if displayContent != null && displayContent.length > 0}
<div class="vault-explorer-feed-card__content">

View file

@ -4,12 +4,17 @@
import { FileRenderData } from "../types";
import License from "src/svelte/shared/services/license";
import FeedCard from "./feed-card.svelte";
import { onMount } from "svelte";
import EventManager from "src/event/event-manager";
import VaultExplorerPlugin from "src/main";
import store from "src/svelte/shared/services/store";
export let enablePremiumFeatures = false;
export let data: FileRenderData[] = [];
export let startIndex;
export let pageLength;
let enableFileIcons = false;
let displayedItems: FileRenderData[] = [];
License.getInstance()
@ -42,7 +47,9 @@
{#if enablePremiumFeatures}
{#each displayedItems as fileData (fileData.path)}
<FeedCard
name={fileData.name}
displayName={fileData.displayName}
extension={fileData.extension}
baseName={fileData.baseName}
path={fileData.path}
tags={fileData.tags}
content={fileData.content}

View file

@ -14,9 +14,13 @@
import { HOVER_LINK_SOURCE_ID } from "src/constants";
import EventManager from "src/event/event-manager";
import ScrollButton from "src/svelte/shared/components/scroll-button.svelte";
import Icon from "src/svelte/shared/components/icon.svelte";
import { getIconIdForFile } from "../services/utils/file-icon-utils";
export let name: string;
export let displayName: string;
export let path: string;
export let baseName: string;
export let extension: string;
export let url: string | null;
export let tags: string[] | null;
export let custom1: string | null;
@ -26,6 +30,7 @@
let tagContainerRef: HTMLDivElement | null;
let wordBreak: WordBreak = "normal";
let enableFileIcons: boolean = false;
let enableScrollButtons: boolean = false;
let renderScrollLeftButton = false;
let renderScrollRightButton = false;
@ -35,6 +40,24 @@
plugin = p;
wordBreak = plugin.settings.titleWrapping;
enableScrollButtons = plugin.settings.enableScrollButtons;
enableFileIcons = plugin.settings.enableFileIcons;
});
onMount(() => {
function handleFileIconsChange() {
enableFileIcons = plugin.settings.enableFileIcons;
}
EventManager.getInstance().on(
"file-icons-setting-change",
handleFileIconsChange,
);
return () => {
EventManager.getInstance().off(
"file-icons-setting-change",
handleFileIconsChange,
);
};
});
onMount(() => {
@ -185,7 +208,12 @@
});
}}
>
{name}
<Stack spacing="xs">
{#if enableFileIcons}
<Icon iconId={getIconIdForFile(baseName, extension)} />
{/if}
<span>{displayName}</span>
</Stack>
</div>
{#if url !== null}
<IconButton iconId="external-link" on:click={handleUrlClick} />

View file

@ -25,8 +25,10 @@
<div class="vault-explorer-grid-view__container">
{#each displayedItems as file (file.path)}
<GridCard
name={file.name}
displayName={file.displayName}
path={file.path}
baseName={file.baseName}
extension={file.extension}
url={file.url}
tags={file.tags}
custom1={file.custom1}

View file

@ -5,14 +5,40 @@
import { HOVER_LINK_SOURCE_ID } from "src/constants";
import Tag from "src/svelte/shared/components/tag.svelte";
import Wrap from "src/svelte/shared/components/wrap.svelte";
import Icon from "src/svelte/shared/components/icon.svelte";
import Stack from "src/svelte/shared/components/stack.svelte";
import { getIconIdForFile } from "../services/utils/file-icon-utils";
import { onMount } from "svelte";
import EventManager from "src/event/event-manager";
export let name: string;
export let displayName: string;
export let baseName: string;
export let extension: string;
export let path: string;
export let tags: string[] | null;
let enableFileIcons: boolean = false;
let plugin: VaultExplorerPlugin;
store.plugin.subscribe((p) => {
plugin = p;
enableFileIcons = plugin.settings.enableFileIcons;
});
onMount(() => {
function handleFileIconsChange() {
enableFileIcons = plugin.settings.enableFileIcons;
}
EventManager.getInstance().on(
"file-icons-setting-change",
handleFileIconsChange,
);
return () => {
EventManager.getInstance().off(
"file-icons-setting-change",
handleFileIconsChange,
);
};
});
function handleTitleClick() {
@ -49,7 +75,12 @@
});
}}
>
{name}
<Stack spacing="xs">
{#if enableFileIcons}
<Icon iconId={getIconIdForFile(baseName, extension)} />
{/if}
<span>{displayName}</span>
</Stack>
</div>
{#if tags != null}
<div class="vault-explorer-list-item__tags">

View file

@ -24,6 +24,12 @@
<div class="vault-explorer-list-view">
{#each displayedItems as file (file.path)}
<ListItem name={file.name} path={file.path} tags={file.tags} />
<ListItem
displayName={file.displayName}
extension={file.extension}
baseName={file.baseName}
path={file.path}
tags={file.tags}
/>
{/each}
</div>

View file

@ -710,9 +710,13 @@
$: renderData = [...filteredTimestamp].sort((a, b) => {
const { value } = sortFilter;
if (value === "file-name-asc") {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
return a.displayName
.toLowerCase()
.localeCompare(b.displayName.toLowerCase());
} else if (value === "file-name-desc") {
return b.name.toLowerCase().localeCompare(a.name.toLowerCase());
return b.displayName
.toLowerCase()
.localeCompare(a.displayName.toLowerCase());
} else if (value === "modified-asc") {
return a.modifiedMillis - b.modifiedMillis;
} else if (value === "modified-desc") {

View file

@ -18,8 +18,11 @@ function createFileContentStore() {
for (let file of files) {
promises.push(
(async () => {
const { extension } = file;
if (extension === "md") {
const { basename, extension } = file;
if (
extension === "md" &&
!basename.endsWith(".excalidraw")
) {
const content = await app.vault.cachedRead(file);
return {
path: file.path,

View file

@ -8,9 +8,9 @@ export const filterBySearch = (file: FileRenderData, value: string) => {
const compare = value.toLowerCase().trim();
const { name, path, content, } = file;
const { displayName, path, content } = file;
if (name.toLowerCase().includes(compare)) {
if (displayName.toLowerCase().includes(compare)) {
return true;
}
@ -23,4 +23,4 @@ export const filterBySearch = (file: FileRenderData, value: string) => {
}
return false;
}
};

View file

@ -0,0 +1,63 @@
import Logger from "js-logger";
export const getIconIdForFile = (baseName: string, extension: string) => {
if (baseName.endsWith(".excalidraw")) {
return "excalidraw-icon";
}
switch (extension) {
case "md":
return "file";
case "canvas":
return "layout-dashboard";
case "zip":
return "file-archive";
case "png":
case "jpg":
case "jpeg":
case "gif":
case "webp":
case "svg":
case "avif":
case "bmp":
return "file-image";
case "mp3":
case "wav":
case "aac":
case "flac":
case "ogg":
case "wma":
case "alac":
case "aiff":
return "file-audio";
case "mp4":
case "avi":
case "mkv":
case "mov":
case "wmv":
case "flv":
case "webm":
case "mpeg":
case "m4v":
case "3gp":
return "file-video";
case "xls":
case "xlsx":
return "file-spreadsheet";
case "xml":
case "json":
return "file-code";
case "ppt":
case "pptx":
return "images";
case "doc":
case "docx":
return "file-type";
case "pdf":
case "txt":
return "file-text";
default:
Logger.warn(`No icon found for file extension: ${extension}`);
return "file";
}
};

View file

@ -95,8 +95,10 @@ export const formatFileDataForRender = (
const displayName = extension === "md" ? basename : name;
return {
name: displayName,
displayName,
baseName: basename,
path,
extension,
url,
content,
tags,

View file

@ -1,6 +1,8 @@
export interface FileRenderData {
name: string;
displayName: string;
path: string;
extension: string;
baseName: string;
content: string | null;
url: string | null;
tags: string[] | null;

View file

@ -1,6 +1,7 @@
<script lang="ts">
import { setIcon } from "obsidian";
import { createEventDispatcher, onMount } from "svelte";
import { getSvgData } from "../services/get-svg-data";
export let ariaLabel = "";
export let iconId = "";
@ -8,21 +9,13 @@
export let noPadding = false;
export let isTabbable = true;
$: svgData = getSvgData(iconId);
function getSvgData(id: string) {
if (id === "ellipsis-vertical") {
return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-ellipsis-vertical"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>`;
}
return "";
}
const dispatch = createEventDispatcher();
let ref: HTMLElement;
// Use onMount to ensure the element is available in the DOM
onMount(() => {
if (iconId === "ellipsis-vertical") return;
if (iconId === "file-pdf") return;
setIcon(ref, iconId);
});
@ -33,6 +26,8 @@
$: className =
"clickable-icon" +
(noPadding == true ? " vault-explorer-icon--no-padding" : "");
$: svgData = getSvgData(iconId);
</script>
<button

View file

@ -1,30 +1,37 @@
<script lang="ts">
import { setIcon } from "obsidian";
import { onMount } from "svelte";
import { getSvgData } from "../services/get-svg-data";
export let ariaLabel = "";
export let iconId = "";
export let xs = false;
let ref: HTMLElement;
let ref: HTMLElement | null = null;
// Use onMount to ensure the element is available in the DOM
onMount(() => {
if (!ref) return;
if (iconId === "ellipsis-vertical") return;
if (iconId === "file-pdf") return;
setIcon(ref, iconId);
const icon = ref.querySelector("svg");
// const icon = ref.querySelector("svg");
if (xs && icon) {
icon.style.setProperty("width", "var(--icon-xs)");
icon.style.setProperty("height", "var(--icon-xs)");
}
// if (xs && icon) {
// icon.style.setProperty("width", "var(--icon-xs)");
// icon.style.setProperty("height", "var(--icon-xs)");
// }
});
$: className = `vault-explorer-icon ${xs ? "vault-explorer-icon--xs" : ""}`;
$: svgData = getSvgData(iconId);
$: className = `vault-explorer-icon ${xs ? "vault-explorer-icon--xs" : "vault-explorer-icon--md"}`;
</script>
<div class={className} aria-label={ariaLabel} bind:this={ref}></div>
<div class={className} aria-label={ariaLabel} bind:this={ref}>
{@html svgData}
</div>
<style>
.vault-explorer-icon {
@ -33,7 +40,13 @@
opacity: var(--icon-opacity);
}
.vault-explorer-icon--md {
width: var(--icon-m);
height: var(--icon-m);
}
.vault-explorer-icon--xs {
width: var(--icon-xs);
height: var(--icon-xs);
}
</style>

View file

@ -0,0 +1,10 @@
const LUCIDE_ELLIPSIS_VERTICAL = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-ellipsis-vertical"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>`;
export const getSvgData = (iconId: string) => {
switch (iconId) {
case "ellipsis-vertical":
return LUCIDE_ELLIPSIS_VERTICAL;
default:
return "";
}
};

View file

@ -40,7 +40,8 @@ export function isVaultExplorerPluginSettings(obj: unknown): obj is VaultExplore
(typedObj["filters"]["sort"]["value"] === "file-name-asc" ||
typedObj["filters"]["sort"]["value"] === "file-name-desc" ||
typedObj["filters"]["sort"]["value"] === "modified-asc" ||
typedObj["filters"]["sort"]["value"] === "modified-desc") &&
typedObj["filters"]["sort"]["value"] === "modified-desc" ||
typedObj["filters"]["sort"]["value"] === "random") &&
(typedObj["filters"]["timestamp"] !== null &&
typeof typedObj["filters"]["timestamp"] === "object" ||
typeof typedObj["filters"]["timestamp"] === "function") &&
@ -616,6 +617,7 @@ export function isVaultExplorerPluginSettings(obj: unknown): obj is VaultExplore
(typedObj["titleWrapping"] === "normal" ||
typedObj["titleWrapping"] === "break-word") &&
typeof typedObj["enableClockUpdates"] === "boolean" &&
typeof typedObj["enableFileIcons"] === "boolean" &&
(typedObj["currentView"] === null ||
typedObj["currentView"] === TExplorerView.DASHBOARD ||
typedObj["currentView"] === TExplorerView.GRID ||

View file

@ -27,6 +27,7 @@ export interface VaultExplorerPluginSettings {
};
titleWrapping: WordBreak;
enableClockUpdates: boolean;
enableFileIcons: boolean;
currentView: TExplorerView | null;
enableScrollButtons: boolean;
pageSize: number;

298
src/types/types-1.20.0.ts Normal file
View file

@ -0,0 +1,298 @@
export interface VaultExplorerPluginSettings_1_20_0 {
properties: {
favorite: string;
url: string;
createdDate: string;
modifiedDate: string;
custom1: string;
custom2: string;
custom3: string;
};
filters: {
search: TSearchFilter;
favorites: TFavoritesFilter;
sort: TSortFilter;
timestamp: TTimestampFilter;
custom: TCustomFilter;
};
views: {
dashboard: TDashboardView;
grid: TGridView;
list: TListView;
table: TTableView;
feed: TFeedView;
recommended: TRecommendedView;
related: TRelatedView;
};
titleWrapping: WordBreak;
enableClockUpdates: boolean;
currentView: TExplorerView | null;
enableScrollButtons: boolean;
pageSize: number;
pluginVersion: string | null;
viewOrder: TExplorerView[];
logLevel: string;
}
interface BaseView {
isEnabled: boolean;
}
interface TTableView extends BaseView {}
interface TListView extends BaseView {}
interface TGridView extends BaseView {}
interface TDashboardView extends BaseView {}
interface TFeedView extends BaseView {}
interface TRecommendedView extends BaseView {}
interface TRelatedView extends BaseView {}
interface BaseFilter {
isEnabled: boolean;
}
interface TSearchFilter extends BaseFilter {
value: string;
}
interface TFavoritesFilter extends BaseFilter {
value: boolean;
}
interface TSortFilter extends BaseFilter {
value: SortFilterOption;
}
interface TTimestampFilter extends BaseFilter {
value: TimestampFilterOption;
}
interface TCustomFilter extends BaseFilter {
selectedGroupId: string;
groups: TFilterGroup[];
}
type TimestampFilterOption =
| "created-today"
| "modified-today"
| "created-this-week"
| "modified-this-week"
| "created-2-weeks"
| "modified-2-weeks"
| "all";
type SortFilterOption =
| "file-name-asc"
| "file-name-desc"
| "modified-asc"
| "modified-desc"
| "random";
type WordBreak = "normal" | "break-word";
enum TExplorerView {
DASHBOARD = "dashboard",
GRID = "grid",
LIST = "list",
FEED = "feed",
TABLE = "table",
RECOMMENDED = "recommended",
RELATED = "related",
}
type FilterOperator = "and" | "or";
enum TextFilterCondition {
IS = "is",
IS_NOT = "is-not",
CONTAINS = "contains",
DOES_NOT_CONTAIN = "does-not-contain",
STARTS_WITH = "starts-with",
ENDS_WITH = "ends-with",
EXISTS = "exists",
DOES_NOT_EXIST = "does-not-exist",
}
enum ListFilterCondition {
CONTAINS = "contains",
DOES_NOT_CONTAIN = "does-not-contain",
EXISTS = "exists",
DOES_NOT_EXIST = "does-not-exist",
}
enum NumberFilterCondition {
IS_EQUAL = "is-equal",
IS_NOT_EQUAL = "is-not-equal",
IS_GREATER = "is-greater",
IS_LESS = "is-less",
IS_GREATER_OR_EQUAL = "is-greater-or-equal",
IS_LESS_OR_EQUAL = "is-less-or-equal",
EXISTS = "exists",
DOES_NOT_EXIST = "does-not-exist",
}
enum CheckboxFilterCondition {
IS = "is",
IS_NOT = "is-not",
EXISTS = "exists",
DOES_NOT_EXIST = "does-not-exist",
}
//TODO: add is between
enum DateFilterCondition {
IS = "is",
IS_BEFORE = "is-before",
IS_AFTER = "is-after",
IS_ON_OR_BEFORE = "is-on-or-before",
IS_ON_OR_AFTER = "is-on-or-after",
EXISTS = "exists",
DOES_NOT_EXIST = "does-not-exist",
}
enum ContentFilterCondition {
CONTAINS = "contains",
DOES_NOT_CONTAIN = "does-not-contain",
IS_EMPTY = "is-empty",
IS_NOT_EMPTY = "is-not-empty",
}
//TODO add is child of and is parent of?
enum FolderFilterCondition {
IS = "is",
IS_NOT = "is-not",
}
enum FileNameFilterCondition {
IS = "is",
IS_NOT = "is-not",
CONTAINS = "contains",
DOES_NOT_CONTAIN = "does-not-contain",
STARTS_WITH = "starts-with",
ENDS_WITH = "ends-with",
}
type FilterCondition =
| TextFilterCondition
| NumberFilterCondition
| DateFilterCondition
| CheckboxFilterCondition
| ListFilterCondition
| ContentFilterCondition
| FolderFilterCondition
| FileNameFilterCondition;
//This matches the Obsidian property types
enum PropertyType {
TEXT = "text",
NUMBER = "number",
LIST = "list",
CHECKBOX = "checkbox",
DATE = "date",
DATETIME = "datetime",
}
enum FilterRuleType {
PROPERTY = "property",
FOLDER = "folder",
FILE_NAME = "file-name",
CONTENT = "content",
}
enum DatePropertyFilterValue {
TODAY = "today",
TOMORROW = "tomorrow",
YESTERDAY = "yesterday",
ONE_WEEK_FROM_NOW = "one-week-from-now",
ONE_WEEK_AGO = "one-week-ago",
ONE_MONTH_FROM_NOW = "one-month-from-now",
ONE_MONTH_AGO = "one-month-ago",
CUSTOM = "custom",
}
interface BaseFilterRule {
id: string;
operator: FilterOperator;
type: FilterRuleType;
condition: FilterCondition;
isEnabled: boolean;
value: string;
matchWhenPropertyDNE: boolean;
}
interface TextPropertyFilterRule extends BaseFilterRule {
type: FilterRuleType.PROPERTY;
propertyType: PropertyType.TEXT;
propertyName: string;
condition: TextFilterCondition;
}
interface NumberPropertyFilterRule extends BaseFilterRule {
type: FilterRuleType.PROPERTY;
propertyType: PropertyType.NUMBER;
propertyName: string;
condition: NumberFilterCondition;
}
interface ListPropertyFilterRule extends BaseFilterRule {
type: FilterRuleType.PROPERTY;
propertyType: PropertyType.LIST;
propertyName: string;
condition: ListFilterCondition;
}
interface CheckboxPropertyFilterRule extends BaseFilterRule {
type: FilterRuleType.PROPERTY;
propertyType: PropertyType.CHECKBOX;
propertyName: string;
condition: CheckboxFilterCondition;
}
interface DatePropertyFilterRule extends BaseFilterRule {
type: FilterRuleType.PROPERTY;
propertyType: PropertyType.DATE | PropertyType.DATETIME;
propertyName: string;
condition: DateFilterCondition;
valueData: string;
}
interface FolderFilterRule extends BaseFilterRule {
type: FilterRuleType.FOLDER;
condition: FolderFilterCondition;
includeSubfolders: boolean;
}
interface FileNameFilterRule extends BaseFilterRule {
type: FilterRuleType.FILE_NAME;
condition: FileNameFilterCondition;
}
interface ContentFilterRule extends BaseFilterRule {
type: FilterRuleType.CONTENT;
condition: ContentFilterCondition;
}
type TFilterRule =
| PropertyFilterRule
| FolderFilterRule
| FileNameFilterRule
| ContentFilterRule;
type PropertyFilterRule =
| TextPropertyFilterRule
| NumberPropertyFilterRule
| ListPropertyFilterRule
| CheckboxPropertyFilterRule
| DatePropertyFilterRule;
interface TFilterGroup {
id: string;
name: string;
rules: TFilterRule[];
isEnabled: boolean;
isSticky: boolean;
}

View file

@ -81,5 +81,6 @@
"1.18.2": "1.4.13",
"1.19.0": "1.4.13",
"1.19.1": "1.4.13",
"1.20.0": "1.4.13"
"1.20.0": "1.4.13",
"1.21.0": "1.4.13"
}