Merge pull request #328 from decaf-dev/dev

* feat: remove image icon placeholder

* feat: remove status bar when view is open

* refactor: rename to image fit to just cover and contain

* feat: remove favorites filter

* feat: set pagination indicator as status bar

* feat: remove timestamp filter

* feat: move search filter up

* feat: add toggle to search

* feat: make groups front and center

* feat: update custom filter button

* chore: bump version

* fix: remove extra padding on buttons

* fix: don't replace existing tab when view opens (#323)

* Clean up code (#324)

* refactor: remove unused code

* feat: clean up old properties

* fix: display without leading zero (#325)

* feat: add created date to sort (#326)

* Update default values (#327)

* feat: add default value to properties

* feat: rename to File system

* refactor: update value

* chore: update guardgen

* chore: update workflow name

* refactor: rename to image cache

* docs: update README.md

* docs: remove unused images
This commit is contained in:
DecafDev 2024-09-10 01:01:22 -06:00 committed by GitHub
commit 716615ccba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 753 additions and 834 deletions

View file

@ -1,4 +1,4 @@
name: Check Console Log
name: Check for console.log statements
on: pull_request

141
README.md
View file

@ -58,14 +58,6 @@ Vault Explorer is a work in progress. Please check the GitHub repository and doc
<img src="readme/custom-filter-options-2.png" width="500">
### Favorites
![](/readme/favorites-filter.png)
### Timestamp filter
![](/readme/timestamp-filter.png)
### Sort filter
![](/readme/sort-filter.png)
@ -95,75 +87,70 @@ Please do not share your license key with anyone. Shared license keys will be de
## Features
| Name | Categories | Documented |
| --------------------------------------------------------------------------- | -------------------- | ---------- |
| Move focus with left and right arrows | Accessibility | ✅ |
| See an image placeholder on the grid card | Cover image | ✅ |
| Load a cover image from the image property | Cover | ✅ |
| Load images from the url property | Cover image | ✅ |
| Load images from the first link in the frontmatter (wiki link or url) | Cover image | ✅ |
| Load images from the first link the body (internal embed or external embed) | Cover image | ✅ |
| Change the order of cover image sources | Cover image, Setting | ✅ |
| Enable/disable cover image sources | Cover image, Setting | ✅ |
| Cache social media images | Cover image | ✅ |
| Filter files by favorite property | Favorites filter | ✅ |
| Favorites support for non-markdown files | Favorites filter | ✅ |
| Add to favorites button in context menu | Favorites filter | ✅ |
| Remove from favorites button in context menu | Favorites filter | ✅ |
| See a favorite icon in the grid card | Favorites filter | ✅ |
| Reorder the filter group list | Filter group | ✅ |
| Add a filter group | Filter group | ✅ |
| Delete a filter group | Filter group | ✅ |
| Set a filter group as sticky | Filter group | ✅ |
| Unset a filter group as sticky | Filter group | ✅ |
| Right click to edit a filter group item | Filter group | |
| Add a filter rule | Filter rule | ✅ |
| Delete a filter rule | Filter rule | ✅ |
| Duplicate a filter rule | Filter rule | ✅ |
| Match when property doesn't exist toggle | Filter rule | ✅ |
| Filter files by folder | Filter rule | ✅ |
| Filter by frontmatter property | Filter rule | ✅ |
| Select a condition | Filter rule | ✅ |
| Select a logical operator: `and`, `or` | Filter rule | ✅ |
| Filter files by folder | Filter rule | ✅ |
| Filter files by file content | Filter rule | ✅ |
| Filter files by file name | Filter rule | ✅ |
| Enable/disable a filter rule | Filter rule | ✅ |
| Set the log level | Logging | ✅ |
| Filter by search | Search filter | ✅ |
| Set page size | Setting | ✅ |
| Set property for modification date | Setting | ✅ |
| Set property for creation date | Setting | ✅ |
| Set a url property | Setting | ✅ |
| Set a favorite property | Setting | ✅ |
| Set custom text property | Setting | ✅ |
| Toggle file icons | Setting | ✅ |
| Change the filter groups wrap | Setting | ✅ |
| Enable/disable loading the social media image | Setting | ✅ |
| Change the collapse style for feed content | Setting | ✅ |
| Remove H1 from feed content | Setting | ✅ |
| Remove markdown from feed content | Setting | ✅ |
| Enable/disable tags on a list item | Setting | ✅ |
| Change the plugin configuration folder | Setting, Data | ✅ |
| Clear the social media cache | Setting, Data | ✅ |
| Change the feed content line clamp for small screens | Setting, Feed view | ✅ |
| Change the feed content line clamp for medium screens | Setting, Feed view | ✅ |
| Change the feed content line clamp for large screens | Setting, Feed view | ✅ |
| Enter a license key | Setting, License | ✅ |
| Sort by title: `asc`, `desc` | Sort order | ✅ |
| Sort by modification date: `asc`, `desc` | Sort order | ✅ |
| Randomly sort files | Sort order | ✅ |
| Filter by creation date: `1 day`, `1 week`, `2 weeks` | Timestamp filter | ✅ |
| Filter by modification date: `1 day`, `1 week`, `2 weeks` | Timestamp filter | ✅ |
| See vault files in a grid view | View | ✅ |
| See vault files in a list view | View | ✅ |
| See vault files in a feed view | View | ✅ |
| Change the view list order | View | |
| Limit displayed files with pagination | View | |
| Search for a tag when clicked | View | ✅ |
| See a file preview when mouse hovers link and ctrl/cmd is pressed | View | |
| Search for property when clicked | View | ✅ |
| See files icons next to the file name | View | ✅ |
| Name | Categories | Documented |
| --------------------------------------------------------------------------- | ---------------------- | ---------- |
| Move focus with left and right arrows | Accessibility | ✅ |
| See an image placeholder on the grid card | Cover image | ✅ |
| Load a cover image from the image property | Cover image | ✅ |
| Load images from the url property | Cover image | ✅ |
| Load images from the first link in the frontmatter (wiki link or url) | Cover image | ✅ |
| Load images from the first link the body (internal embed or external embed) | Cover image | ✅ |
| Cache social media images | Cover image | ✅ |
| Change the order of cover image sources | Cover image, Setting | ✅ |
| Enable/disable cover image sources | Cover image, Setting | ✅ |
| Enable/disable loading the social media image | Cover image, Setting | ✅ |
| Clear the social media cache | Cover image, Setting | ✅ |
| Change the collapse style for feed content | Feed view, Setting | ✅ |
| Remove H1 from feed content | Feed view, Setting | ✅ |
| Remove markdown from feed content | Feed view, Setting | ✅ |
| Change the feed content line clamp for small screens | Feed view, Setting | ✅ |
| Change the feed content line clamp for medium screens | Feed view, Setting | ✅ |
| Change the feed content line clamp for large screens | Feed view, Setting | ✅ |
| See files icons next to the file name | File icons | ✅ |
| Toggle file icons | File icons, Setting | ✅ |
| Reorder the filter group list | Filter group | ✅ |
| Add a filter group | Filter group | ✅ |
| Delete a filter group | Filter group | ✅ |
| Set a filter group as sticky | Filter group | ✅ |
| Unset a filter group as sticky | Filter group | ✅ |
| Right click to edit a filter group item | Filter group | |
| Change the filter groups wrap | Filter groups, Setting | ✅ |
| Add a filter rule | Filter rule | ✅ |
| Delete a filter rule | Filter rule | ✅ |
| Duplicate a filter rule | Filter rule | ✅ |
| Match when property doesn't exist toggle | Filter rule | ✅ |
| Filter files by folder | Filter rule | ✅ |
| Filter by frontmatter property | Filter rule | ✅ |
| Select a condition | Filter rule | ✅ |
| Select a logical operator: `and`, `or` | Filter rule | ✅ |
| Filter files by folder | Filter rule | ✅ |
| Filter files by file content | Filter rule | ✅ |
| Filter files by file name | Filter rule | ✅ |
| Enable/disable a filter rule | Filter rule | ✅ |
| Enter a license key | License, Setting | ✅ |
| Enable/disable tags on a list item | List view, Setting | ✅ |
| Set the log level | Logging | ✅ |
| Filter by search | Search filter | ✅ |
| Change the plugin configuration folder | Setting | ✅ |
| Sort by title: `asc`, `desc` | Sort order | ✅ |
| Sort by modification date: `asc`, `desc` | Sort order | ✅ |
| Randomly sort files | Sort order | ✅ |
| Set property for modification date | Setting | ✅ |
| Set property for creation date | Setting | ✅ |
| See vault files in a grid view | View | ✅ |
| See vault files in a list view | View | ✅ |
| See vault files in a feed view | View | ✅ |
| See vault files in a table view | View | ✅ |
| Change the view list order | View | |
| Limit displayed files with pagination | View | |
| Search for a tag when clicked | View | ✅ |
| See a file preview when mouse hovers link and ctrl/cmd is pressed | View | |
| Search for property when clicked | View | ✅ |
| Load tags from the body of a markdown notes | View | ✅ |
| Set page size | View, Setting | ✅ |
| Set a url property | View, Setting | ✅ |
| Set custom text property | View, Setting | ✅ |
| Enable/disable load tags from a markdown note | View, Setting | ✅ |
## Network use

View file

@ -1,7 +1,7 @@
{
"id": "vault-explorer",
"name": "Vault Explorer",
"version": "1.41.1",
"version": "1.42.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.41.1",
"version": "1.42.0",
"description": "Explore your vault in visual format",
"main": "main.js",
"scripts": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

View file

@ -7,10 +7,9 @@ export const HOVER_LINK_SOURCE_ID = "vault-explorer-preview";
export const DEFAULT_SETTINGS: VaultExplorerPluginSettings = {
properties: {
favorite: "",
url: "",
image: "",
coverImageFit: "",
url: "url",
image: "image",
coverImageFit: "image-fit",
createdDate: "",
modifiedDate: "",
custom1: "",
@ -22,14 +21,6 @@ export const DEFAULT_SETTINGS: VaultExplorerPluginSettings = {
isEnabled: true,
value: "",
},
favorites: {
isEnabled: true,
value: false,
},
timestamp: {
isEnabled: true,
value: "all",
},
sort: {
isEnabled: true,
value: "file-name-asc",
@ -94,8 +85,6 @@ export const DEFAULT_SETTINGS: VaultExplorerPluginSettings = {
enableClockUpdates: true,
enableFileIcons: false,
loadBodyTags: true,
filterGroupsWidth: "300px",
shouldWrapFilterGroups: false,
shouldCollapseFilters: false,
pageSize: 25,
viewOrder: [

View file

@ -21,7 +21,6 @@ export enum PluginEvent {
VIEW_TOGGLE_SETTING_CHANGE = "view-toggle-setting-change",
FILE_ICONS_SETTING_CHANGE = "file-icons-setting-change",
LOAD_BODY_TAGS_SETTING_CHANGE = "load-body-tags-setting-change",
WRAP_FILTER_GROUPS_SETTING_CHANGE = "wrap-filter-groups-setting-change",
LOAD_SOCIAL_MEDIA_IMAGE_SETTING_CHANGE = "load-social-media-image-setting-change",
SHOW_TAGS_SETTING_CHANGE = "show-tags-setting-change",
}

View file

@ -20,6 +20,7 @@ import { isVersionLessThan } from "./utils";
import License from "./svelte/shared/services/license";
import { clearSocialMediaImageCache } from "./svelte/app/services/social-media-image-cache";
import store from "./svelte/shared/services/store";
import "./styles.css";
export default class VaultExplorerPlugin extends Plugin {
settings: VaultExplorerPluginSettings = DEFAULT_SETTINGS;
@ -63,6 +64,22 @@ export default class VaultExplorerPlugin extends Plugin {
}
private registerEvents() {
this.registerEvent(
this.app.workspace.on("active-leaf-change", () => {
const explorerLeaf =
this.app.workspace.getActiveViewOfType(VaultExplorerView);
const statusBar = document.querySelector(
".status-bar"
) as HTMLElement | null;
if (!statusBar) return;
if (explorerLeaf) {
statusBar.classList.add("vault-explorer-status-bar");
} else {
statusBar.classList.remove("vault-explorer-status-bar");
}
})
);
//Callback if the file is renamed or moved
//This callback is already debounced by Obsidian
this.registerEvent(
@ -228,7 +245,7 @@ export default class VaultExplorerPlugin extends Plugin {
const leaf = leaves[0];
this.app.workspace.revealLeaf(leaf);
} else {
this.app.workspace.getLeaf().setViewState({
this.app.workspace.getLeaf("tab").setViewState({
type: VAULT_EXPLORER_VIEW,
active: true,
});

View file

@ -31,6 +31,7 @@ import Migrate_1_38_0 from "./migrate_1_38_0";
import Migrate_1_39_0 from "./migrate_1_39_0";
import Migrate_1_40_0 from "./migrate_1_40_0";
import Migrate_1_41_0 from "./migrate_1_41_0";
import Migrate_1_42_0 from "./migrate_1_42_0";
const migrations: TMigration[] = [
{
@ -183,6 +184,11 @@ const migrations: TMigration[] = [
to: "1.41.0",
migrate: Migrate_1_41_0,
},
{
from: "1.41.1",
to: "1.42.0",
migrate: Migrate_1_42_0,
},
];
export const preformMigrations = (

View file

@ -0,0 +1,18 @@
import { VaultExplorerPluginSettings } from "src/types";
import MigrationInterface from "./migration_interface";
import { VaultExplorerPluginSettings_1_41_1 } from "src/types/types-1.41.1";
export default class Migrate_1_42_0 implements MigrationInterface {
migrate(data: Record<string, unknown>) {
const typedData = data as unknown as VaultExplorerPluginSettings_1_41_1;
const newData: VaultExplorerPluginSettings = {
...typedData,
};
delete (newData as any).filterGroupsWidth;
delete (newData as any).shouldWrapFilterGroups;
delete (newData as any).filters.favorites;
delete (newData as any).filters.timestamp;
return newData as unknown as Record<string, unknown>;
}
}

View file

@ -1,4 +1,4 @@
import { App } from "obsidian"
import { App } from "obsidian";
import { ObsidianProperty, ObsidianPropertyType } from "./types";
/**
@ -6,17 +6,34 @@ import { ObsidianProperty, ObsidianPropertyType } from "./types";
* NOTE: This is an undocumented API function and may break in future versions of Obsidian
*/
export const getAllObsidianProperties = (app: App) => {
const properties = (app as any).metadataTypeManager.getAllProperties() as Record<string, ObsidianProperty>;
return Object.values(properties).sort((a: ObsidianProperty, b: ObsidianProperty) => a.name.localeCompare(b.name));
const properties = (
app as any
).metadataTypeManager.getAllProperties() as Record<
string,
ObsidianProperty
>;
return Object.values(properties).sort(
(a: ObsidianProperty, b: ObsidianProperty) =>
a.name.localeCompare(b.name)
);
};
export const getObsidianPropertiesByType = (app: App, type: ObsidianPropertyType) => {
export const getObsidianPropertiesByType = (
app: App,
type: ObsidianPropertyType
) => {
return getAllObsidianProperties(app).filter((p) => p.type === type);
}
};
export const getDropdownOptionsForProperties = (properties: ObsidianProperty[]) => {
return properties.reduce((acc: Record<string, string>, cur: ObsidianProperty) => {
acc[cur.name] = cur.name;
return acc;
}, { "": "Select a property" });
}
export const getDropdownOptionsForProperties = (
properties: ObsidianProperty[],
defaultValue: Record<string, string> = { "": "Select a property" }
) => {
return properties.reduce(
(acc: Record<string, string>, cur: ObsidianProperty) => {
acc[cur.name] = cur.name;
return acc;
},
defaultValue
);
};

View file

@ -108,20 +108,6 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
})
);
new Setting(containerEl)
.setName("Wrap filter groups")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.shouldWrapFilterGroups)
.onChange(async (value) => {
this.plugin.settings.shouldWrapFilterGroups = value;
await this.plugin.saveSettings();
EventManager.getInstance().emit(
PluginEvent.WRAP_FILTER_GROUPS_SETTING_CHANGE
);
})
);
new Setting(containerEl).setName("Filters").setHeading();
new Setting(containerEl).setName("Search filter").addToggle((toggle) =>
@ -136,36 +122,6 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
})
);
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(
PluginEvent.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(
PluginEvent.FILTER_TOGGLE_SETTING_CHANGE
);
})
);
new Setting(containerEl).setName("Sort filter").addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.filters.sort.isEnabled)
@ -490,26 +446,6 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
new Setting(containerEl).setName("Built-in properties").setHeading();
new Setting(containerEl)
.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(
PluginEvent.PROPERTY_SETTING_CHANGE
);
})
);
new Setting(containerEl)
.setName("Cover image property")
.setDesc(
@ -517,7 +453,11 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
)
.addDropdown((dropdown) =>
dropdown
.addOptions(getDropdownOptionsForProperties(textProperties))
.addOptions(
getDropdownOptionsForProperties(textProperties, {
image: "image",
})
)
.setValue(this.plugin.settings.properties.image)
.onChange(async (value) => {
this.plugin.settings.properties.image = value;
@ -535,7 +475,11 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
)
.addDropdown((dropdown) =>
dropdown
.addOptions(getDropdownOptionsForProperties(textProperties))
.addOptions(
getDropdownOptionsForProperties(textProperties, {
"image-fit": "image-fit",
})
)
.setValue(this.plugin.settings.properties.coverImageFit)
.onChange(async (value) => {
this.plugin.settings.properties.coverImageFit = value;
@ -553,7 +497,11 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
)
.addDropdown((dropdown) =>
dropdown
.addOptions(getDropdownOptionsForProperties(textProperties))
.addOptions(
getDropdownOptionsForProperties(textProperties, {
url: "url",
})
)
.setValue(this.plugin.settings.properties.url)
.onChange(async (value) => {
this.plugin.settings.properties.url = value;
@ -569,7 +517,7 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
text: "Property used to store a 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.",
text: "If set, the property will be preferred over the file's creation date.",
});
new Setting(containerEl)
@ -598,7 +546,7 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
text: "Property used to store a 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.",
text: "If set, the property will be preferred over the file's modification date.",
});
new Setting(containerEl)
@ -744,16 +692,14 @@ export default class VaultExplorerSettingsTab extends PluginSettingTab {
})
);
new Setting(containerEl)
.setName("Social media image cache")
.addButton((button) =>
button
.setClass("mod-destructive")
.setButtonText("Clear cache")
.onClick(async () => {
await clearSocialMediaImageCache();
})
);
new Setting(containerEl).setName("Image cache").addButton((button) =>
button
.setClass("mod-destructive")
.setButtonText("Clear cache")
.onClick(async () => {
await clearSocialMediaImageCache();
})
);
}
onClose() {

3
src/styles.css Normal file
View file

@ -0,0 +1,3 @@
.vault-explorer-status-bar {
display: none;
}

View file

@ -1,7 +0,0 @@
<script lang="ts">
import Checkbox from "src/svelte/shared/components/checkbox.svelte";
export let value: boolean;
</script>
<Checkbox id="favorites" label="Favorites" {value} on:change />

View file

@ -35,7 +35,6 @@
export let path: string;
export let createdMillis: number;
export let content: string | null;
export let isFavorite: boolean | null;
let ref: HTMLElement | null = null;
let enableFileIcons = false;
@ -146,16 +145,9 @@
openInCurrentTab(plugin, path);
}
function handleFavoriteChange(filePath: string, value: boolean) {
dispatch("favoritePropertyChange", { filePath, value });
}
function handleCardContextMenu(e: Event) {
const nativeEvent = e as MouseEvent;
openContextMenu(plugin, path, nativeEvent, {
isFavorite,
onFavoriteChange: handleFavoriteChange,
});
openContextMenu(plugin, path, nativeEvent, {});
}
function handleCardMouseOver(e: MouseEvent) {

View file

@ -46,8 +46,6 @@
path={fileRenderData.path}
content={fileRenderData.content}
createdMillis={fileRenderData.createdMillis}
isFavorite={fileRenderData.isFavorite}
on:favoritePropertyChange
/>
{/each}
{/if}

View file

@ -3,115 +3,50 @@
import _ from "lodash";
import VaultExplorerPlugin from "src/main";
import store from "src/svelte/shared/services/store";
import { onMount } from "svelte";
import Wrap from "src/svelte/shared/components/wrap.svelte";
import EventManager from "src/event/event-manager";
import { PluginEvent } from "src/event/types";
import FilterGroup from "./filter-group.svelte";
import Stack from "src/svelte/shared/components/stack.svelte";
export let groups: TFilterGroup[] = [];
let containerRef: HTMLDivElement | null = null;
let shouldWrapFilterGroups: boolean = false;
const debounceSaveContainerWidth = _.debounce(saveContainerWidth, 200);
let plugin: VaultExplorerPlugin;
store.plugin.subscribe((p) => {
plugin = p;
shouldWrapFilterGroups = plugin.settings.shouldWrapFilterGroups;
});
onMount(() => {
function handleWrapFilterGroupsSettingChange() {
shouldWrapFilterGroups = plugin.settings.shouldWrapFilterGroups;
}
EventManager.getInstance().on(
PluginEvent.WRAP_FILTER_GROUPS_SETTING_CHANGE,
handleWrapFilterGroupsSettingChange,
);
return () => {
EventManager.getInstance().off(
PluginEvent.WRAP_FILTER_GROUPS_SETTING_CHANGE,
handleWrapFilterGroupsSettingChange,
);
};
});
onMount(() => {
let resizeObserver: ResizeObserver;
if (containerRef) {
containerRef.style.width = plugin.settings.filterGroupsWidth;
resizeObserver = new ResizeObserver(() => {
debounceSaveContainerWidth();
});
resizeObserver.observe(containerRef);
}
return () => {
resizeObserver?.disconnect();
};
});
async function saveContainerWidth() {
if (containerRef) {
plugin.settings.filterGroupsWidth = containerRef.style.width;
await plugin.saveSettings();
}
}
</script>
<div class="vault-explorer-filter-group-list">
<div
class="vault-explorer-filter-group-list__container"
bind:this={containerRef}
>
<Stack spacing="md" width="fit-content">
{#if groups.length > 0}
<Wrap
spacingX="sm"
spacingY="sm"
wrap={shouldWrapFilterGroups ? "wrap" : "nowrap"}
>
{#each groups as group (group.id)}
<FilterGroup
id={group.id}
name={group.name}
isSelected={group.isEnabled}
isSticky={group.isSticky}
on:groupClick
on:groupDrop
on:groupDragOver
on:groupDragStart
on:groupContextMenu
/>
{/each}
</Wrap>
{#each groups as group (group.id)}
<FilterGroup
id={group.id}
name={group.name}
isSelected={group.isEnabled}
isSticky={group.isSticky}
on:groupClick
on:groupDrop
on:groupDragOver
on:groupDragStart
on:groupContextMenu
/>
{/each}
{/if}
{#if groups.length === 0}
<span class="vault-explorer-empty-label">No groups</span>
<span class="vault-explorer-empty-label">No groups to display</span>
{/if}
</div>
</Stack>
</div>
<style>
.vault-explorer-filter-group-list {
width: 100%;
position: relative;
}
.vault-explorer-filter-group-list__container {
padding-bottom: 8px;
resize: horizontal;
width: min(100%, 700px);
overflow-x: auto;
max-width: 100%;
}
.vault-explorer-empty-label {
color: var(--text-faint);
font-size: var(--font-smaller);
font-size: var(--font-md);
margin-left: 4px;
}
</style>

View file

@ -66,18 +66,16 @@
<style>
.vault-explorer-filter-group {
display: flex;
align-items: center;
width: 150px;
height: 75px;
white-space: nowrap;
font-size: var(--tag-size);
font-weight: var(--tag-weight);
text-decoration: var(--tag-decoration);
padding: var(--tag-padding-y) var(--tag-padding-x);
line-height: 1;
border-radius: var(--tag-radius);
padding: 4px 8px;
border-radius: var(--radius-m);
color: var(--text-faint);
border: 1px solid var(--background-modifier-border);
background-color: var(--background-primary);
pointer-events: auto;
}
.vault-explorer-filter-group:focus-visible {

View file

@ -35,7 +35,6 @@
export let custom1: string | null;
export let custom2: string | null;
export let custom3: string | null;
export let isFavorite: boolean | null;
export let coverImageFit: CoverImageFit;
let plugin: VaultExplorerPlugin;
@ -134,10 +133,6 @@
openInCurrentTab(plugin, path);
}
function handleFavoriteChange(filePath: string, value: boolean) {
dispatch("favoritePropertyChange", { filePath, value });
}
function handleCoverImageFitChange(filePath: string, value: CoverImageFit) {
dispatch("coverImageFitChange", { filePath, value });
}
@ -147,9 +142,7 @@
const showCoverImageOptions = path.endsWith(".md");
openContextMenu(plugin, path, nativeEvent, {
isFavorite,
coverImageFit: showCoverImageOptions ? coverImageFit : undefined,
onFavoriteChange: handleFavoriteChange,
onCoverImageFitChange: showCoverImageOptions
? handleCoverImageFitChange
: undefined,
@ -229,14 +222,9 @@
{/if}
{/each}
{#if imageUrl === null}
<div class="vault-explorer-grid-card__cover-icon">
<Icon
iconId={getIconIdForFile(baseName, extension)}
size="lg"
/>
</div>
<div class="vault-explorer-grid-card__image"></div>
{/if}
{#if isFavorite === true}
<!-- {#if isFavorite === true}
<div class="vault-explorer-grid-card__favorite">
<Flex
justify="center"
@ -247,7 +235,7 @@
<Icon iconId="star" ariaLabel="Favorite" />
</Flex>
</div>
{/if}
{/if} -->
</div>
<div class="vault-explorer-grid-card__body">
<div
@ -353,14 +341,7 @@
border-top-right-radius: var(--radius-m);
}
.vault-explorer-grid-card__cover-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.vault-explorer-grid-card__favorite {
/* .vault-explorer-grid-card__favorite {
position: absolute;
top: 8px;
right: 8px;
@ -368,7 +349,7 @@
height: 20px;
background-color: var(--background-primary);
border-radius: 50%;
}
} */
.vault-explorer-grid-card__body {
padding: 8px 16px;

View file

@ -35,8 +35,6 @@
custom1={fileRenderData.custom1}
custom2={fileRenderData.custom2}
custom3={fileRenderData.custom3}
isFavorite={fileRenderData.isFavorite}
on:favoritePropertyChange
on:coverImageFitChange
/>
{/each}

View file

@ -6,7 +6,7 @@
import Icon from "src/svelte/shared/components/icon.svelte";
import Stack from "src/svelte/shared/components/stack.svelte";
import { getIconIdForFile } from "../services/file-icon";
import { createEventDispatcher, onMount } from "svelte";
import { onMount } from "svelte";
import EventManager from "src/event/event-manager";
import { PluginEvent } from "src/event/types";
import { openContextMenu } from "../services/context-menu";
@ -18,7 +18,6 @@
export let extension: string;
export let path: string;
export let tags: string[] | null;
export let isFavorite: boolean | null;
export let showTags: boolean;
export let isSmallScreenSize: boolean;
@ -26,30 +25,28 @@
let ref: HTMLElement | null = null;
let plugin: VaultExplorerPlugin;
const dispatch = createEventDispatcher();
store.plugin.subscribe((p) => {
plugin = p;
enableFileIcons = plugin.settings.enableFileIcons;
// showTags = plugin.settings.views.list.showTags;
showTags = plugin.settings.views.list.showTags;
});
// onMount(() => {
// function handleShowTagsChange() {
// showTags = plugin.settings.views.list.showTags;
// }
onMount(() => {
function handleShowTagsChange() {
showTags = plugin.settings.views.list.showTags;
}
// EventManager.getInstance().on(
// PluginEvent.SHOW_TAGS_SETTING_CHANGE,
// handleShowTagsChange,
// );
// return () => {
// EventManager.getInstance().off(
// PluginEvent.SHOW_TAGS_SETTING_CHANGE,
// handleShowTagsChange,
// );
// };
// });
EventManager.getInstance().on(
PluginEvent.SHOW_TAGS_SETTING_CHANGE,
handleShowTagsChange,
);
return () => {
EventManager.getInstance().off(
PluginEvent.SHOW_TAGS_SETTING_CHANGE,
handleShowTagsChange,
);
};
});
onMount(() => {
function handleFileIconsChange() {
@ -76,16 +73,9 @@
openInCurrentTab(plugin, path);
}
function handleFavoriteChange(filePath: string, value: boolean) {
dispatch("favoritePropertyChange", { filePath, value });
}
function handleItemContextMenu(e: Event) {
const nativeEvent = e as MouseEvent;
openContextMenu(plugin, path, nativeEvent, {
isFavorite,
onFavoriteChange: handleFavoriteChange,
});
openContextMenu(plugin, path, nativeEvent, {});
}
function handleItemMouseOver(e: MouseEvent) {

View file

@ -33,8 +33,6 @@
{isSmallScreenSize}
path={fileRenderData.path}
tags={fileRenderData.tags}
isFavorite={fileRenderData.isFavorite}
on:favoritePropertyChange
/>
{/each}
</div>

View file

@ -53,50 +53,67 @@
</script>
<div class="vault-explorer-pagination-indicator" bind:this={ref}>
<Stack
align="center"
direction={isWrapped ? "row-reverse" : "row"}
justify="flex-end"
spacing="md"
>
<Stack spacing="xs">
<div class="vault-explorer-pagination-indicator__container">
<Stack
align="center"
direction={isWrapped ? "row-reverse" : "row"}
justify="flex-end"
spacing="md"
>
<Flex>
<IconButton
iconId="chevrons-left"
ariaLabel="First page"
on:click={() => handlePageChange(1)}
/>
<IconButton
iconId="chevron-left"
ariaLabel="Previous page"
disabled={currentPage === 1}
on:click={() => handlePageChange(currentPage - 1)}
/>
<IconButton
iconId="chevron-right"
ariaLabel="Next page"
disabled={currentPage === totalPages}
on:click={() => handlePageChange(currentPage + 1)}
/>
<IconButton
iconId="chevrons-right"
ariaLabel="Last page"
on:click={() => handlePageChange(totalPages)}
/>
</Flex>
<Stack spacing="xs">
<span>{startIndex + 1}</span>
<span>-</span>
<span>{endIndex}</span>
<Stack spacing="xs">
<span>{startIndex + 1}</span>
<span>-</span>
<span>{endIndex}</span>
</Stack>
<span>of</span>
<span>{totalItems}</span>
</Stack>
<span>of</span>
<span>{totalItems}</span>
</Stack>
<Flex>
<IconButton
iconId="chevrons-left"
ariaLabel="First page"
on:click={() => handlePageChange(1)}
/>
<IconButton
iconId="chevron-left"
ariaLabel="Previous page"
disabled={currentPage === 1}
on:click={() => handlePageChange(currentPage - 1)}
/>
<IconButton
iconId="chevron-right"
ariaLabel="Next page"
disabled={currentPage === totalPages}
on:click={() => handlePageChange(currentPage + 1)}
/>
<IconButton
iconId="chevrons-right"
ariaLabel="Last page"
on:click={() => handlePageChange(totalPages)}
/>
</Flex>
</Stack>
</div>
</div>
<style>
.vault-explorer-pagination-indicator {
.vault-explorer-pagination-indicator__container {
flex: 1;
position: fixed;
bottom: 0px;
right: 0px;
display: flex;
border-style: solid;
border-width: var(--status-bar-border-width);
border-color: var(--status-bar-border-color);
background-color: var(--status-bar-background);
color: var(--status-bar-text-color);
font-size: var(--status-bar-font-size);
padding: var(--size-4-1);
}
.vault-explorer-pagination-indicator {
display: relative;
}
</style>

View file

@ -1,36 +1,67 @@
<script lang="ts">
import IconButton from "src/svelte/shared/components/icon-button.svelte";
import Stack from "src/svelte/shared/components/stack.svelte";
import { createEventDispatcher } from "svelte";
export let value: string;
let inputRef: HTMLInputElement | null;
let hidden: boolean = true;
const dispatch = createEventDispatcher();
function handleClearClick() {
dispatch("clear", { value: "" });
}
function handleIconClick() {
hidden = !hidden;
if (!hidden) {
requestAnimationFrame(() => {
inputRef?.focus();
});
}
}
</script>
<div class="vault-explorer-search-filter">
<input type="text" placeholder="Search..." {value} on:input />
{#if value.length > 0}
<div
tabindex="0"
role="button"
aria-label="Clear search"
class="search-input-clear-button"
on:click={() => handleClearClick()}
on:keydown={(e) => {
if (e.key === "Enter") {
handleClearClick();
}
}}
<Stack spacing="sm">
<div
class="vault-explorer-search-filter"
style="display: {hidden ? 'none' : 'block'};"
>
<input
bind:this={inputRef}
type="text"
placeholder="Search..."
{value}
on:input
/>
{/if}
</div>
{#if value.length > 0}
<div
tabindex="0"
role="button"
aria-label="Clear search"
class="search-input-clear-button"
on:click={() => handleClearClick()}
on:keydown={(e) => {
if (e.key === "Enter") {
handleClearClick();
}
}}
/>
{/if}
</div>
<IconButton
iconId="search"
ariaLabel="Search"
on:click={() => handleIconClick()}
/>
</Stack>
<style>
.vault-explorer-search-filter input[type="text"] {
width: 100%;
height: 24px;
}
.vault-explorer-search-filter {

View file

@ -38,6 +38,17 @@
item.onClick(() => handleValueChange("modified-asc"));
});
menu.addSeparator();
menu.addItem((item) => {
item.setTitle("Created time (new to old)");
item.setChecked(value === "created-desc");
item.onClick(() => handleValueChange("created-desc"));
});
menu.addItem((item) => {
item.setTitle("Created time (old to new)");
item.setChecked(value === "created-asc");
item.onClick(() => handleValueChange("created-asc"));
});
menu.addSeparator();
menu.addItem((item) => {
item.setTitle("Random");
item.setChecked(value === "random");

View file

@ -42,11 +42,6 @@
key: "tags",
label: "Tags",
},
{
key: "isFavorite",
label: "Favorite",
format: (value: unknown) => (value === true ? "Yes" : "No"),
},
{
key: "createdMillis",
label: "Created",
@ -85,10 +80,6 @@
};
});
function handleFavoriteChange(filePath: string, value: boolean) {
dispatch("favoritePropertyChange", { filePath, value });
}
function handleRowClick(path: string) {
if (plugin === null) return;
@ -108,18 +99,11 @@
});
}
function handleRowContextMenu(
e: Event,
path: string,
isFavorite: boolean | null,
) {
function handleRowContextMenu(e: Event, path: string) {
if (plugin === null) return;
const nativeEvent = e as MouseEvent;
openContextMenu(plugin, path, nativeEvent, {
isFavorite,
onFavoriteChange: handleFavoriteChange,
});
openContextMenu(plugin, path, nativeEvent, {});
}
function getValue(item: FileRenderData, column: TColumn): unknown {
@ -171,11 +155,7 @@
}}
on:focus={() => {}}
on:contextmenu={(e) =>
handleRowContextMenu(
e,
filteredItem.path,
filteredItem.isFavorite,
)}
handleRowContextMenu(e, filteredItem.path)}
on:mouseover={(e) =>
handleRowMouseOver(e, filteredItem.path)}
>

View file

@ -1,67 +0,0 @@
<script lang="ts">
import { Menu } from "obsidian";
import IconButton from "src/svelte/shared/components/icon-button.svelte";
import { TimestampFilterOption } from "src/types";
import { createEventDispatcher } from "svelte";
export let value: TimestampFilterOption;
const dispatch = createEventDispatcher();
function handleValueChange(value: TimestampFilterOption) {
dispatch("change", { value });
}
function handleButtonClick(e: CustomEvent) {
const nativeEvent = e.detail.nativeEvent as MouseEvent;
const menu = new Menu();
menu.setUseNativeMenu(true);
menu.addItem((item) => {
item.setTitle("All");
item.setChecked(value === "all");
item.onClick(() => handleValueChange("all"));
});
menu.addSeparator();
menu.addItem((item) => {
item.setTitle("Modified today");
item.setChecked(value === "modified-today");
item.onClick(() => handleValueChange("modified-today"));
});
menu.addItem((item) => {
item.setTitle("Created today");
item.setChecked(value === "created-today");
item.onClick(() => handleValueChange("created-today"));
});
menu.addSeparator();
menu.addItem((item) => {
item.setTitle("Modifed this week");
item.setChecked(value === "modified-this-week");
item.onClick(() => handleValueChange("modified-this-week"));
});
menu.addItem((item) => {
item.setTitle("Created this week");
item.setChecked(value === "created-this-week");
item.onClick(() => handleValueChange("created-this-week"));
});
menu.addSeparator();
menu.addItem((item) => {
item.setTitle("Modifed 2 weeks");
item.setChecked(value === "modified-2-weeks");
item.onClick(() => handleValueChange("modified-2-weeks"));
});
menu.addItem((item) => {
item.setTitle("Created 2 weeks");
item.setChecked(value === "created-2-weeks");
item.onClick(() => handleValueChange("created-2-weeks"));
});
menu.showAtMouseEvent(nativeEvent);
}
</script>
<IconButton
ariaLabel="Change timestamp filter"
iconId="clock"
on:click={handleButtonClick}
/>

View file

@ -4,16 +4,13 @@
// ============================================
import Stack from "../shared/components/stack.svelte";
import Flex from "../shared/components/flex.svelte";
import FavoritesFilter from "./components/favorites-filter.svelte";
import TabList from "../shared/components/tab-list.svelte";
import Tab from "../shared/components/tab.svelte";
import { Notice, TFile } from "obsidian";
import {
TCustomFilter,
TFavoritesFilter,
TSearchFilter,
TSortFilter,
TTimestampFilter,
TExplorerView,
CoverImageFit,
} from "src/types";
@ -21,9 +18,7 @@
import VaultExplorerPlugin from "src/main";
import GridView from "./components/grid-view.svelte";
import ListView from "./components/list-view.svelte";
import { filterByFavorites } from "./services/filters/favorite-filter";
import { filterBySearch } from "./services/filters/search-filter";
import { filterByTimestamp } from "./services/filters/timestamp-filter";
import { filterByGroups } from "./services/filters/custom/filter-by-groups";
import { formatFileDataForRender } from "./services/render-data";
import _ from "lodash";
@ -38,7 +33,6 @@
import { FileRenderData } from "./types";
import Logger from "js-logger";
import SearchFilter from "./components/search-filter.svelte";
import TimestampFilter from "./components/timestamp-filter.svelte";
import SortFilter from "./components/sort-filter.svelte";
import { DEBOUNCE_INPUT_TIME, SCREEN_SIZE_MD } from "./constants";
import FeedView from "./components/feed-view.svelte";
@ -82,14 +76,6 @@
isEnabled: true,
value: "",
};
let favoritesFilter: TFavoritesFilter = {
isEnabled: false,
value: false,
};
let timestampFilter: TTimestampFilter = {
isEnabled: true,
value: "all",
};
let sortFilter: TSortFilter = {
isEnabled: true,
value: "file-name-asc",
@ -146,9 +132,7 @@
shouldCollapseFilters = settings.shouldCollapseFilters;
pageSize = settings.pageSize;
searchFilter = settings.filters.search;
favoritesFilter = settings.filters.favorites;
sortFilter = settings.filters.sort;
timestampFilter = settings.filters.timestamp;
currentView = settings.currentView;
customFilter = settings.filters.custom;
viewOrder = settings.viewOrder;
@ -171,9 +155,7 @@
});
searchFilter = plugin.settings.filters.search;
favoritesFilter = plugin.settings.filters.favorites;
sortFilter = plugin.settings.filters.sort;
timestampFilter = plugin.settings.filters.timestamp;
customFilter = plugin.settings.filters.custom;
}
@ -579,10 +561,6 @@
searchFilter.value = e.target.value;
}, DEBOUNCE_INPUT_TIME);
const debounceFavoriteFilterChange = _.debounce((value) => {
favoritesFilter.value = value;
}, DEBOUNCE_INPUT_TIME);
function handleReshuffleClick() {
randomFileSortStore.load(plugin.app);
}
@ -619,8 +597,6 @@
async function saveSettings() {
plugin.settings.filters.search = searchFilter;
plugin.settings.filters.sort = sortFilter;
plugin.settings.filters.timestamp = timestampFilter;
plugin.settings.filters.favorites = favoritesFilter;
plugin.settings.currentView = currentView;
plugin.settings.filters.custom = customFilter;
plugin.settings.viewOrder = viewOrder;
@ -672,20 +648,11 @@
customFilter.groups = newGroups;
}
function handleListViewTagsToggle() {
showListViewTags = !showListViewTags;
}
function handleViewDragOver(e: CustomEvent) {
const { nativeEvent } = e.detail;
nativeEvent.preventDefault();
}
function handleTimestampFilterChange(e: CustomEvent) {
const { value } = e.detail;
timestampFilter.value = value;
}
function handleViewDragStart(e: CustomEvent, id: string) {
const { nativeEvent } = e.detail;
nativeEvent.dataTransfer.setData("text", id);
@ -778,12 +745,6 @@
sortFilter.value = value;
}
function handleFavoritesChange(e: CustomEvent) {
const nativeEvent = e.detail.nativeEvent;
const value = (nativeEvent.target as HTMLInputElement).checked;
debounceFavoriteFilterChange(value);
}
function handleCoverImageFitChange(e: CustomEvent) {
const { filePath, value } = e.detail as {
filePath: string;
@ -793,14 +754,6 @@
const { properties } = plugin.settings;
const { coverImageFit: coverImageFitProperty } = properties;
//If the favorite property is not set, return
if (coverImageFitProperty === "") {
new Notice(
"Vault Explorer: Please set a cover image fit property in the plugin settings to use this feature",
);
return;
}
const file = plugin.app.vault.getFileByPath(filePath);
if (!file) {
Logger.error({
@ -819,43 +772,6 @@
}
}
function handleFavoritePropertyChange(e: CustomEvent) {
const { filePath, value } = e.detail as {
filePath: string;
value: boolean;
};
const { properties } = plugin.settings;
const { favorite: favoritePropertyName } = properties;
//If the favorite property is not set, return
if (favoritePropertyName === "") {
new Notice(
"Vault Explorer: Please set a favorite property in the plugin settings to use this feature",
);
return;
}
const file = plugin.app.vault.getFileByPath(filePath);
if (!file) {
Logger.error({
fileName: "app/index.svelte",
functionName: "handleFavoritePropertyChange",
message: "file not found. returning...",
});
return;
}
if (file.extension === "md") {
plugin.app.fileManager.processFrontMatter(file, (frontmatter) => {
frontmatter[favoritePropertyName] = value;
return frontmatter;
});
} else {
favoritesStore.setFavorite(filePath, value);
}
}
function handleCustomFilterClick() {
new CustomFilterModal(plugin).open();
}
@ -895,7 +811,6 @@
) {
formatted = filteredCustom.map((loadedFile) => {
const { id, file } = loadedFile;
const isFavorite = favoritesCache.get(file.path) ?? null;
const content = contentCache.get(file.path) ?? null;
return formatFileDataForRender({
@ -904,7 +819,6 @@
fileId: id,
file,
fileContent: content,
fileFavorite: isFavorite,
});
});
}
@ -917,27 +831,7 @@
return true;
});
$: filteredFavorites = filteredSearch.filter((file) => {
const { isEnabled, value } = favoritesFilter;
if (isEnabled) {
return filterByFavorites(file, value);
}
return true;
});
$: filteredTimestamp = filteredFavorites.filter((file) => {
const { modifiedMillis, createdMillis } = file;
return filterByTimestamp({
value: timestampFilter.value,
createdMillis,
modifiedMillis,
startOfTodayMillis,
startOfThisWeekMillis,
startOfLastWeekMillis,
});
});
$: renderData = [...filteredTimestamp].sort((a, b) => {
$: renderData = [...filteredSearch].sort((a, b) => {
const { value } = sortFilter;
if (value === "file-name-asc") {
return a.displayName
@ -951,6 +845,10 @@
return a.modifiedMillis - b.modifiedMillis;
} else if (value === "modified-desc") {
return b.modifiedMillis - a.modifiedMillis;
} else if (value === "created-asc") {
return a.createdMillis - b.createdMillis;
} else if (value === "created-desc") {
return b.createdMillis - a.createdMillis;
} else if (value === "random") {
const sortKeyA = randomSortCache.get(a.path) ?? 0;
const sortKeyB = randomSortCache.get(b.path) ?? 0;
@ -966,8 +864,6 @@
//and save the settings again
$: searchFilter,
sortFilter,
timestampFilter,
favoritesFilter,
currentView,
customFilter,
viewOrder,
@ -992,64 +888,25 @@
<div class="vault-explorer" bind:this={ref}>
{#if shouldCollapseFilters === false}
<div class="vault-explorer-filters">
<Stack spacing="md" direction="column">
{#if searchFilter.isEnabled}
<SearchFilter
value={searchFilter.value}
on:input={debounceSearchFilterChange}
on:clear={() => (searchFilter.value = "")}
/>
{/if}
<Stack direction="column" spacing="sm">
<Flex justify="space-between">
<Stack spacing="sm">
{#if favoritesFilter.isEnabled}
<FavoritesFilter
value={favoritesFilter.value}
on:change={handleFavoritesChange}
/>
{/if}
<Flex>
{#if timestampFilter.isEnabled}
<TimestampFilter
value={timestampFilter.value}
on:change={handleTimestampFilterChange}
/>
{/if}
{#if sortFilter.isEnabled}
<SortFilter
value={sortFilter.value}
on:change={handleSortChange}
/>
{/if}
{#if sortFilter.value == "random"}
<IconButton
iconId="shuffle"
ariaLabel="Reshuffle files"
on:click={handleReshuffleClick}
/>
{/if}
<IconButton
ariaLabel="Change custom filter"
iconId="list-filter"
on:click={handleCustomFilterClick}
/>
</Flex>
</Stack>
</Flex>
<Flex justify="space-between">
{#if customFilter.isEnabled}
<FilterGroupList
groups={customFilter.groups}
on:groupClick={handleGroupClick}
on:groupContextMenu={handleGroupContextMenu}
on:groupDrop={handleGroupDrop}
on:groupDragOver={handleGroupDragOver}
on:groupDragStart={handleGroupDragStart}
/>
{/if}
</Flex>
</Stack>
<Stack spacing="sm" direction="column">
<Flex justify="space-between">
{#if customFilter.isEnabled}
<FilterGroupList
groups={customFilter.groups}
on:groupClick={handleGroupClick}
on:groupContextMenu={handleGroupContextMenu}
on:groupDrop={handleGroupDrop}
on:groupDragOver={handleGroupDragOver}
on:groupDragStart={handleGroupDragStart}
/>
{/if}
</Flex>
<div>
<button
class="vault-explorer-button"
on:click={handleCustomFilterClick}>Configure</button
>
</div>
</Stack>
<Spacer size="md" />
</div>
@ -1081,24 +938,28 @@
/> -->
<!-- </Stack> -->
</div>
<Stack spacing="sm">
{#if currentView === "list"}
<IconButton
iconId="tags"
ariaLabel="Toggle tags"
on:click={handleListViewTagsToggle}
<Flex>
{#if searchFilter.isEnabled}
<SearchFilter
value={searchFilter.value}
on:input={debounceSearchFilterChange}
on:clear={() => (searchFilter.value = "")}
/>
<Divider direction="vertical" />
{/if}
<PaginationIndicator
{startIndex}
{endIndex}
{currentPage}
{totalPages}
{totalItems}
on:change={handlePageChange}
/>
</Stack>
{#if sortFilter.isEnabled}
<SortFilter
value={sortFilter.value}
on:change={handleSortChange}
/>
{/if}
{#if sortFilter.value == "random"}
<IconButton
iconId="shuffle"
ariaLabel="Reshuffle files"
on:click={handleReshuffleClick}
/>
{/if}
</Flex>
</Wrap>
<Spacer size="md" />
{#if currentView === "grid"}
@ -1106,7 +967,6 @@
data={renderData}
{startIndex}
{pageLength}
on:favoritePropertyChange={handleFavoritePropertyChange}
on:coverImageFitChange={handleCoverImageFitChange}
/>
{:else if currentView === "list"}
@ -1116,23 +976,20 @@
showTags={showListViewTags}
{startIndex}
{pageLength}
on:favoritePropertyChange={handleFavoritePropertyChange}
/>
{:else if currentView === "table"}
<TableView
data={renderData}
{startIndex}
{pageLength}
on:favoritePropertyChange={handleFavoritePropertyChange}
/>
<TableView data={renderData} {startIndex} {pageLength} />
{:else if currentView === "feed"}
<FeedView
data={renderData}
{startIndex}
{pageLength}
on:favoritePropertyChange={handleFavoritePropertyChange}
/>
<FeedView data={renderData} {startIndex} {pageLength} />
{/if}
<PaginationIndicator
{startIndex}
{endIndex}
{currentPage}
{totalPages}
{totalItems}
on:change={handlePageChange}
/>
</div>
<style>
@ -1144,4 +1001,14 @@
.vault-explorer-view-select {
flex: 1;
}
.vault-explorer-button {
background: none;
text-decoration: underline;
color: var(--text-faint);
font-size: var(--font-small);
border: none;
box-shadow: none;
padding: 0;
}
</style>

View file

@ -7,14 +7,10 @@ export const openContextMenu = (
filePath: string,
e: MouseEvent,
{
isFavorite,
coverImageFit,
onFavoriteChange,
onCoverImageFitChange,
}: {
isFavorite: boolean | null;
coverImageFit?: CoverImageFit;
onFavoriteChange: (filePath: string, value: boolean) => void;
onCoverImageFitChange?: (
filePath: string,
value: CoverImageFit
@ -35,25 +31,15 @@ export const openContextMenu = (
item.setTitle("Open in new window");
item.onClick(() => openInNewWindow(plugin, filePath));
});
menu.addSeparator();
menu.addItem((item) => {
item.setTitle(
isFavorite ? "Remove from favorites" : "Add to favorites"
);
item.onClick(() => {
const newValue = isFavorite !== null ? !isFavorite : true;
onFavoriteChange(filePath, newValue);
});
});
if (coverImageFit !== undefined && onCoverImageFitChange !== undefined) {
menu.addSeparator();
menu.addItem((item) => {
item.setTitle("Cover image fit: cover");
item.setTitle("Cover");
item.setChecked(coverImageFit === "cover");
item.onClick(() => onCoverImageFitChange(filePath, "cover"));
});
menu.addItem((item) => {
item.setTitle("Cover image fit: contain");
item.setTitle("Contain");
item.setChecked(coverImageFit === "contain");
item.onClick(() => onCoverImageFitChange(filePath, "contain"));
});

View file

@ -1,11 +0,0 @@
import { FileRenderData } from "../../types";
export const filterByFavorites = (
file: FileRenderData,
onlyFavorites: boolean
) => {
if (onlyFavorites) {
return file.isFavorite;
}
return true;
};

View file

@ -1,34 +0,0 @@
import { TimestampFilterOption } from "src/types";
interface FilterByTimestampParams {
value: TimestampFilterOption;
createdMillis: number;
modifiedMillis: number;
startOfTodayMillis: number;
startOfThisWeekMillis: number;
startOfLastWeekMillis: number;
}
export const filterByTimestamp = ({
value,
createdMillis,
modifiedMillis,
startOfTodayMillis,
startOfThisWeekMillis,
startOfLastWeekMillis,
}: FilterByTimestampParams) => {
if (value === "modified-this-week") {
return modifiedMillis > startOfThisWeekMillis;
} else if (value === "created-this-week") {
return createdMillis > startOfThisWeekMillis;
} else if (value === "modified-2-weeks") {
return modifiedMillis > startOfLastWeekMillis;
} else if (value === "created-2-weeks") {
return createdMillis > startOfLastWeekMillis;
} else if (value === "modified-today") {
return modifiedMillis > startOfTodayMillis;
} else if (value === "created-today") {
return createdMillis > startOfTodayMillis;
}
return true;
}

View file

@ -46,14 +46,12 @@ export const formatFileDataForRender = ({
file,
fileId,
fileContent,
fileFavorite,
}: {
app: App;
settings: VaultExplorerPluginSettings;
file: TFile;
fileId: string;
fileContent: string | null;
fileFavorite: boolean | null;
}): FileRenderData => {
const { name, basename, extension, path } = file;
@ -67,7 +65,6 @@ export const formatFileDataForRender = ({
url: urlProp,
image: imageProp,
coverImageFit: coverImageFitProp,
favorite: favoriteProp,
custom1: custom1Prop,
custom2: custom2Prop,
custom3: custom3Prop,
@ -96,17 +93,6 @@ export const formatFileDataForRender = ({
PropertyType.TEXT
);
let isFavorite: boolean | null;
if (fileFavorite === null) {
isFavorite = loadPropertyValue<boolean>(
fileFrontmatter,
favoriteProp,
PropertyType.CHECKBOX
);
} else {
isFavorite = fileFavorite;
}
let coverImageFit: CoverImageFit | null = loadPropertyValue<string>(
fileFrontmatter,
coverImageFitProp,
@ -250,7 +236,6 @@ export const formatFileDataForRender = ({
content: fileContent,
tags,
imageUrl,
isFavorite,
createdMillis,
modifiedMillis,
custom1,

View file

@ -61,9 +61,9 @@ export const clearSocialMediaImageCache = async () => {
try {
const db = await openDatabase();
await db.clear(STORE_NAME);
new Notice("Vault Explorer: social media image cache cleared");
new Notice("Vault Explorer: Image cache cleared");
} catch (err) {
new Notice("Failed to clear social media image cache");
new Notice("Vault Explorer: Failed to clear image cache");
const error = err as Error;
Logger.error(
{

View file

@ -19,7 +19,7 @@ export const formatAsBearTimeString = (milliseconds: number) => {
return `${diffInHours} hours ago`;
}
if (diffInDays === 1) {
return `Yesterday at ${time.format("hh:mm A")}`;
return `Yesterday at ${time.format("h:mm A")}`;
}
if (time.year() === now.year()) {
return time.format("MMMM D");

View file

@ -12,7 +12,6 @@ export interface FileRenderData {
url: string | null;
imageUrl: string | null;
tags: string[] | null;
isFavorite: boolean | null;
createdMillis: number;
modifiedMillis: number;
custom1: string | null;

View file

@ -26,6 +26,7 @@
}
$: className = getClassName(noPadding);
$: hasSlotContent = !!$$slots.default;
</script>
<button
@ -34,9 +35,14 @@
{disabled}
aria-label={ariaLabel}
on:click={handleClick}
><Icon {iconId} {size} />
<div class="vault-explorer-icon-button__text"><slot /></div></button
>
<Icon {iconId} {size} />
{#if hasSlotContent}
<div class="vault-explorer-icon-button__text">
<slot />
</div>
{/if}
</button>
<style>
.vault-explorer-icon-button--no-padding {

View file

@ -13,7 +13,6 @@ export function isVaultExplorerPluginSettings(obj: unknown): obj is VaultExplore
(typedObj["properties"] !== null &&
typeof typedObj["properties"] === "object" ||
typeof typedObj["properties"] === "function") &&
typeof typedObj["properties"]["favorite"] === "string" &&
typeof typedObj["properties"]["url"] === "string" &&
typeof typedObj["properties"]["image"] === "string" &&
typeof typedObj["properties"]["coverImageFit"] === "string" &&
@ -30,11 +29,6 @@ export function isVaultExplorerPluginSettings(obj: unknown): obj is VaultExplore
typeof typedObj["filters"]["search"] === "function") &&
typeof typedObj["filters"]["search"]["isEnabled"] === "boolean" &&
typeof typedObj["filters"]["search"]["value"] === "string" &&
(typedObj["filters"]["favorites"] !== null &&
typeof typedObj["filters"]["favorites"] === "object" ||
typeof typedObj["filters"]["favorites"] === "function") &&
typeof typedObj["filters"]["favorites"]["isEnabled"] === "boolean" &&
typeof typedObj["filters"]["favorites"]["value"] === "boolean" &&
(typedObj["filters"]["sort"] !== null &&
typeof typedObj["filters"]["sort"] === "object" ||
typeof typedObj["filters"]["sort"] === "function") &&
@ -43,18 +37,9 @@ export function isVaultExplorerPluginSettings(obj: unknown): obj is VaultExplore
typedObj["filters"]["sort"]["value"] === "file-name-desc" ||
typedObj["filters"]["sort"]["value"] === "modified-asc" ||
typedObj["filters"]["sort"]["value"] === "modified-desc" ||
typedObj["filters"]["sort"]["value"] === "created-asc" ||
typedObj["filters"]["sort"]["value"] === "created-desc" ||
typedObj["filters"]["sort"]["value"] === "random") &&
(typedObj["filters"]["timestamp"] !== null &&
typeof typedObj["filters"]["timestamp"] === "object" ||
typeof typedObj["filters"]["timestamp"] === "function") &&
typeof typedObj["filters"]["timestamp"]["isEnabled"] === "boolean" &&
(typedObj["filters"]["timestamp"]["value"] === "created-today" ||
typedObj["filters"]["timestamp"]["value"] === "modified-today" ||
typedObj["filters"]["timestamp"]["value"] === "created-this-week" ||
typedObj["filters"]["timestamp"]["value"] === "modified-this-week" ||
typedObj["filters"]["timestamp"]["value"] === "created-2-weeks" ||
typedObj["filters"]["timestamp"]["value"] === "modified-2-weeks" ||
typedObj["filters"]["timestamp"]["value"] === "all") &&
(typedObj["filters"]["custom"] !== null &&
typeof typedObj["filters"]["custom"] === "object" ||
typeof typedObj["filters"]["custom"] === "function") &&
@ -608,9 +593,7 @@ export function isVaultExplorerPluginSettings(obj: unknown): obj is VaultExplore
typeof e["isEnabled"] === "boolean"
) &&
(typedObj["views"]["grid"]["coverImageFit"] === "cover" ||
typedObj["views"]["grid"]["coverImageFit"] === "contain" ||
typedObj["views"]["grid"]["coverImageFit"] === "scale-down" ||
typedObj["views"]["grid"]["coverImageFit"] === "none") &&
typedObj["views"]["grid"]["coverImageFit"] === "contain") &&
typeof typedObj["views"]["grid"]["loadSocialMediaImage"] === "boolean" &&
(typedObj["views"]["list"] !== null &&
typeof typedObj["views"]["list"] === "object" ||
@ -653,8 +636,6 @@ export function isVaultExplorerPluginSettings(obj: unknown): obj is VaultExplore
typedObj["currentView"] === TExplorerView.RECOMMENDED ||
typedObj["currentView"] === TExplorerView.RELATED) &&
typeof typedObj["pageSize"] === "number" &&
typeof typedObj["filterGroupsWidth"] === "string" &&
typeof typedObj["shouldWrapFilterGroups"] === "boolean" &&
typeof typedObj["shouldCollapseFilters"] === "boolean" &&
Array.isArray(typedObj["viewOrder"]) &&
typedObj["viewOrder"].every((e: any) =>

View file

@ -1,7 +1,6 @@
/** @see {isVaultExplorerPluginSettings} ts-auto-guard:type-guard */
export interface VaultExplorerPluginSettings {
properties: {
favorite: string;
url: string;
image: string;
coverImageFit: string;
@ -13,9 +12,7 @@ export interface VaultExplorerPluginSettings {
};
filters: {
search: TSearchFilter;
favorites: TFavoritesFilter;
sort: TSortFilter;
timestamp: TTimestampFilter;
custom: TCustomFilter;
};
views: {
@ -33,8 +30,6 @@ export interface VaultExplorerPluginSettings {
loadBodyTags: boolean;
currentView: TExplorerView | null;
pageSize: number;
filterGroupsWidth: string;
shouldWrapFilterGroups: boolean;
shouldCollapseFilters: boolean;
viewOrder: TExplorerView[];
configDir: string;
@ -97,37 +92,22 @@ export interface TSearchFilter extends BaseFilter {
value: string;
}
export interface TFavoritesFilter extends BaseFilter {
value: boolean;
}
export interface TSortFilter extends BaseFilter {
value: SortFilterOption;
}
export interface TTimestampFilter extends BaseFilter {
value: TimestampFilterOption;
}
export interface TCustomFilter extends BaseFilter {
selectedGroupId: string;
groups: TFilterGroup[];
}
export type TimestampFilterOption =
| "created-today"
| "modified-today"
| "created-this-week"
| "modified-this-week"
| "created-2-weeks"
| "modified-2-weeks"
| "all";
export type SortFilterOption =
| "file-name-asc"
| "file-name-desc"
| "modified-asc"
| "modified-desc"
| "created-asc"
| "created-desc"
| "random";
export type WordBreak = "normal" | "break-word";

332
src/types/types-1.41.1.ts Normal file
View file

@ -0,0 +1,332 @@
export interface VaultExplorerPluginSettings_1_41_1 {
properties: {
favorite: string;
url: string;
image: string;
coverImageFit: 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;
enableFileIcons: boolean;
loadBodyTags: boolean;
currentView: TExplorerView | null;
pageSize: number;
filterGroupsWidth: string;
shouldWrapFilterGroups: boolean;
shouldCollapseFilters: boolean;
viewOrder: TExplorerView[];
configDir: string;
pluginVersion: string | null;
logLevel: string;
}
type FileInteractionStyle = "title" | "content";
interface BaseView {
isEnabled: boolean;
}
interface TTableView extends BaseView {}
interface TListView extends BaseView {
showTags: boolean;
}
interface TGridView extends BaseView {
coverImageSources: CoverImageSource[];
coverImageFit: CoverImageFit;
loadSocialMediaImage: boolean;
}
interface CoverImageSource {
type: CoverImageSourceType;
isEnabled: boolean;
}
type CoverImageFit = "cover" | "contain";
type CoverImageSourceType =
| "image-property"
| "url-property"
| "frontmatter"
| "body";
interface TDashboardView extends BaseView {}
type CollapseStyle = "no-new-lines" | "no-extra-new-lines";
interface TFeedView extends BaseView {
collapseStyle: CollapseStyle;
removeH1: boolean;
lineClampSmall: number;
lineClampMedium: number;
lineClampLarge: number;
}
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",
}
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",
}
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

@ -9,30 +9,22 @@
"modifiedDate": "modification",
"createdDate": "creation",
"image": "image",
"coverImageFit": ""
"coverImageFit": "image-fit"
},
"filters": {
"search": {
"isEnabled": true,
"value": ""
},
"timestamp": {
"isEnabled": true,
"value": "all"
},
"sort": {
"isEnabled": true,
"value": "file-name-asc"
"value": "modified-desc"
},
"custom": {
"isEnabled": true,
"selectedGroupId": "",
"groups": []
},
"favorites": {
"isEnabled": true,
"value": false
},
"properties": {
"groups": []
}
@ -94,8 +86,6 @@
"enableClockUpdates": true,
"enableFileIcons": true,
"loadBodyTags": true,
"filterGroupsWidth": "69px",
"shouldWrapFilterGroups": false,
"shouldCollapseFilters": false,
"pageSize": 25,
"viewOrder": [
@ -105,6 +95,6 @@
"feed"
],
"configDir": ".vaultexplorer",
"pluginVersion": "1.41.0",
"pluginVersion": "1.42.0",
"logLevel": "trace"
}

View file

@ -136,5 +136,6 @@
"1.40.1": "1.4.13",
"1.40.2": "1.4.13",
"1.41.0": "1.4.13",
"1.41.1": "1.4.13"
"1.41.1": "1.4.13",
"1.42.0": "1.4.13"
}