From 754d5002d4376b609b7575602f16c5b54fe193e1 Mon Sep 17 00:00:00 2001 From: Aleix Soler Date: Fri, 21 Nov 2025 17:10:10 +0100 Subject: [PATCH] feat: fronttmatter tag by template --- components/SettingsUI/BehaviourSettings.tsx | 12 + .../FrontmatterMappingsSettings.tsx | 315 +++++++++++++++++ components/StatusDashboard/useVaultStats.tsx | 5 +- constants/defaultSettings.ts | 1 + core/commandsService.ts | 68 +++- core/noteStatusService.ts | 330 +++++++++++++----- .../file-explorer-integration.tsx | 1 + integrations/status-bar/status-bar.tsx | 3 + .../toolbar/editorToolbarIntegration.tsx | 3 + integrations/views/grouped-dashboard-view.tsx | 6 +- integrations/views/grouped-status-view.tsx | 6 +- integrations/views/status-dashboard-view.tsx | 28 +- styles.css | 2 +- styles/components/settings.css | 72 ++++ types/pluginSettings.ts | 16 + utils/frontmatterMappings.ts | 111 ++++++ 16 files changed, 864 insertions(+), 115 deletions(-) create mode 100644 components/SettingsUI/FrontmatterMappingsSettings.tsx create mode 100644 utils/frontmatterMappings.ts diff --git a/components/SettingsUI/BehaviourSettings.tsx b/components/SettingsUI/BehaviourSettings.tsx index 4a741d5..00aea38 100644 --- a/components/SettingsUI/BehaviourSettings.tsx +++ b/components/SettingsUI/BehaviourSettings.tsx @@ -1,6 +1,7 @@ import { PluginSettings } from "@/types/pluginSettings"; import React from "react"; import { SettingItem } from "./SettingItem"; +import { FrontmatterMappingsSettings } from "./FrontmatterMappingsSettings"; export type Props = { settings: PluginSettings; @@ -91,6 +92,17 @@ export const BehaviourSettings: React.FC = ({ settings, onChange }) => { /> + + + + void; +}; + +type StatusOption = { + label: string; + value: string; +}; + +const isStatusMapping = ( + mapping: StatusFrontmatterMapping, +): mapping is StatusFrontmatterMapping & { scope: "status" } => + mapping.scope === "status"; + +const generateId = () => + typeof crypto !== "undefined" && crypto.randomUUID + ? crypto.randomUUID() + : `${Date.now()}-${Math.random().toString(36).slice(2)}`; + +const sanitizeKeys = (raw: string): string[] => + raw + .split(",") + .map((key) => key.trim()) + .filter((key) => Boolean(key)); + +const buildStatusIdentifier = ( + templateId: string | undefined, + statusName: string, +): string => { + if (!templateId) { + return statusName; + } + return `${templateId}:${statusName}`; +}; + +export const FrontmatterMappingsSettings: React.FC = ({ + settings, + onChange, +}) => { + const mappings = settings.statusFrontmatterMappings || []; + + const templateOptions = useMemo( + () => + (settings.templates || []).map((template) => ({ + value: template.id, + label: template.name || template.id, + })), + [settings.templates], + ); + + const statusOptions = useMemo(() => { + const templateStatuses = + settings.templates?.flatMap((template) => + template.statuses.map((status) => ({ + value: buildStatusIdentifier(template.id, status.name), + label: `${status.name} (${template.name || template.id})`, + })), + ) || []; + + const customStatuses = + settings.customStatuses?.map((status) => ({ + value: status.name, + label: `${status.name} (Custom)`, + })) || []; + + return [...templateStatuses, ...customStatuses]; + }, [settings.templates, settings.customStatuses]); + + const updateMappings = (nextMappings: StatusFrontmatterMapping[]) => { + onChange("statusFrontmatterMappings", nextMappings); + }; + + const handleMappingChange = ( + id: string, + updater: ( + mapping: StatusFrontmatterMapping, + ) => StatusFrontmatterMapping, + ) => { + updateMappings( + mappings.map((mapping) => + mapping.id === id ? updater(mapping) : mapping, + ), + ); + }; + + const handleScopeChange = ( + id: string, + scope: StatusFrontmatterMapping["scope"], + ) => { + handleMappingChange(id, (mapping) => { + const frontmatterKeys = mapping.frontmatterKeys || []; + + if (scope === "template") { + const templateId = + mapping.scope === "template" + ? mapping.templateId + : templateOptions[0]?.value || ""; + + return { + id: mapping.id, + scope: "template", + templateId, + frontmatterKeys, + }; + } + + const defaultStatusOption = statusOptions[0]; + const templateId = isStatusMapping(mapping) + ? mapping.templateId + : defaultStatusOption?.value.includes(":") + ? defaultStatusOption.value.split(":", 2)[0] + : undefined; + const statusName = isStatusMapping(mapping) + ? mapping.statusName || "" + : defaultStatusOption?.value?.split(":")?.pop() || ""; + + return { + id: mapping.id, + scope: "status", + templateId, + statusName, + frontmatterKeys, + }; + }); + }; + + const handleTemplateChange = (id: string, templateId: string) => { + handleMappingChange(id, (mapping) => ({ + ...mapping, + scope: "template", + templateId, + })); + }; + + const handleStatusChange = (id: string, identifier: string) => { + const [maybeTemplateId, name] = identifier.includes(":") + ? identifier.split(":", 2) + : [undefined, identifier]; + handleMappingChange(id, (mapping) => ({ + ...mapping, + templateId: maybeTemplateId, + statusName: name, + scope: "status", + })); + }; + + const handleKeysChange = (id: string, raw: string) => { + const keys = sanitizeKeys(raw); + handleMappingChange(id, (mapping) => ({ + ...mapping, + frontmatterKeys: keys, + })); + }; + + const handleRemove = (id: string) => { + updateMappings(mappings.filter((mapping) => mapping.id !== id)); + }; + + const handleAdd = () => { + const newMapping: StatusFrontmatterMapping = { + id: generateId(), + scope: "template", + templateId: templateOptions[0]?.value || "", + frontmatterKeys: [], + }; + updateMappings([...mappings, newMapping]); + }; + + return ( +
+ {mappings.length === 0 && ( +
+ No mappings configured yet. +
+ )} + {mappings.map((mapping) => { + const currentKeys = mapping.frontmatterKeys || []; + const keysValue = currentKeys.join(", "); + const selectedStatus = + mapping.scope === "status" + ? buildStatusIdentifier( + mapping.templateId, + mapping.statusName || "", + ) + : undefined; + + return ( +
+
+
+ + +
+ + {mapping.scope === "template" ? ( +
+ + +
+ ) : ( +
+ + +
+ )} + +
+ + + handleKeysChange( + mapping.id, + event.target.value, + ) + } + /> + + Separate multiple keys with commas. Include + the default key if you still want to write + to it. + +
+
+ +
+ +
+
+ ); + })} + + +
+ ); +}; diff --git a/components/StatusDashboard/useVaultStats.tsx b/components/StatusDashboard/useVaultStats.tsx index 22b9aaf..8c588f7 100644 --- a/components/StatusDashboard/useVaultStats.tsx +++ b/components/StatusDashboard/useVaultStats.tsx @@ -6,6 +6,7 @@ import { NoteStatusService, } from "@/core/noteStatusService"; import settingsService from "@/core/settingsService"; +import { getKnownFrontmatterKeys } from "@/utils/frontmatterMappings"; export interface VaultStats { totalNotes: number; @@ -33,7 +34,9 @@ export const useVaultStats = () => { const files = BaseNoteStatusService.app.vault.getFiles(); const availableStatuses = BaseNoteStatusService.getAllAvailableStatuses(); - const statusMetadataKeys = [settingsService.settings.tagPrefix]; + const statusMetadataKeys = getKnownFrontmatterKeys( + settingsService.settings, + ); let notesWithStatus = 0; const statusDistribution: Record = {}; diff --git a/constants/defaultSettings.ts b/constants/defaultSettings.ts index 8a4631e..46df02b 100644 --- a/constants/defaultSettings.ts +++ b/constants/defaultSettings.ts @@ -51,4 +51,5 @@ export const DEFAULT_PLUGIN_SETTINGS: PluginSettings = { editorToolbarButtonPosition: "right", // Default position on the right editorToolbarButtonDisplay: "all-notes", // Default to show button in all notes applyStatusRecursivelyToSubfolders: false, + statusFrontmatterMappings: [], }; diff --git a/core/commandsService.ts b/core/commandsService.ts index 9a1ea49..62d407e 100644 --- a/core/commandsService.ts +++ b/core/commandsService.ts @@ -4,6 +4,8 @@ import settingsService from "./settingsService"; import eventBus from "./eventBus"; import { VIEW_TYPE_EXAMPLE } from "../integrations/views/grouped-status-view"; import { isExperimentalFeatureEnabled } from "@/utils/experimentalFeatures"; +import { GroupedStatuses, NoteStatus } from "@/types/noteStatus"; +import { getKnownFrontmatterKeys } from "@/utils/frontmatterMappings"; export class CommandsService { private plugin: Plugin; @@ -71,23 +73,20 @@ export class CommandsService { // Get the current file data const statusService = this.createStatusService(file); statusService.populateStatuses(); - const statuses = - statusService.statuses[ - settingsService.settings.tagPrefix - ] || []; - const currentStatus = statuses.length - ? statuses?.[0].name - : undefined; + const flattened = this.flattenStatuses( + statusService.statuses, + ); + const currentStatus = flattened[0]; let nextIndex = 0; if (currentStatus) { const currentIndex = allStatuses.findIndex( - (s) => s.name === currentStatus, + (s) => + s.name === currentStatus.name && + (s.templateId || null) === + (currentStatus.templateId || null), ); if (currentIndex !== -1) { - nextIndex = - currentIndex === -1 - ? 0 - : (currentIndex + 1) % allStatuses.length; + nextIndex = (currentIndex + 1) % allStatuses.length; } } @@ -214,10 +213,17 @@ export class CommandsService { if (!statuses || statuses.length === 0) return false; - const tagPrefix = settingsService.settings.tagPrefix; - const queries = statuses.map( - (status) => `[${tagPrefix}:"${status}"]`, + const keys = getKnownFrontmatterKeys( + settingsService.settings, ); + const queries = Array.from( + new Set( + statuses.flatMap((status) => + keys.map((key) => `[${key}:"${status}"]`), + ), + ), + ); + if (!queries.length) return false; const query = queries.join(" OR "); // @ts-ignore @@ -340,12 +346,38 @@ export class CommandsService { }); } + private flattenStatuses( + groupedStatuses: GroupedStatuses | null | undefined, + ): NoteStatus[] { + if (!groupedStatuses) { + return []; + } + const seen = new Set(); + const flattened: NoteStatus[] = []; + Object.values(groupedStatuses).forEach((list) => { + list.forEach((status) => { + const identifier = BaseNoteStatusService.formatStatusIdentifier( + { + templateId: status.templateId, + name: status.name, + }, + ); + if (!seen.has(identifier)) { + seen.add(identifier); + flattened.push(status); + } + }); + }); + return flattened; + } + private getFileStatuses(file: TFile): string[] { const statusService = this.createStatusService(file); statusService.populateStatuses(); - const tagPrefix = settingsService.settings.tagPrefix; - const statuses = statusService.statuses[tagPrefix] || []; - return statuses.length > 0 ? statuses.map((s) => s.name) : ["unknown"]; + const flattened = this.flattenStatuses(statusService.statuses); + return flattened.length > 0 + ? flattened.map((s) => s.name) + : ["unknown"]; } /** diff --git a/core/noteStatusService.ts b/core/noteStatusService.ts index e35afa2..82b5632 100644 --- a/core/noteStatusService.ts +++ b/core/noteStatusService.ts @@ -10,6 +10,10 @@ import settingsService from "@/core/settingsService"; import eventBus from "./eventBus"; import statusStoreManager from "./statusStoreManager"; import type { StatusStore } from "./statusStores/types"; +import { + getKnownFrontmatterKeys, + resolveFrontmatterKeysForStatus, +} from "@/utils/frontmatterMappings"; export abstract class BaseNoteStatusService { static app: App; @@ -129,6 +133,39 @@ export class NoteStatusService extends BaseNoteStatusService { this.statusStore = statusStoreManager.getStoreForFile(file); } + private isMarkdownFile(): boolean { + return this.file.extension === "md"; + } + + private getKeysForReading(): string[] { + const defaultKey = settingsService.settings.tagPrefix; + if (!this.isMarkdownFile()) { + return [defaultKey]; + } + const knownKeys = getKnownFrontmatterKeys(settingsService.settings); + return [defaultKey, ...knownKeys.filter((key) => key !== defaultKey)]; + } + + private collectIdentifiersForFile(): string[] { + const keysToRead = this.getKeysForReading(); + const seen = new Set(); + const identifiers: string[] = []; + + keysToRead.forEach((key) => { + const values = this.statusStore.getStatuses(this.file, key); + values.forEach((value) => { + const normalized = value?.toString(); + if (!normalized || seen.has(normalized)) { + return; + } + seen.add(normalized); + identifiers.push(normalized); + }); + }); + + return identifiers; + } + private shouldStoreStatusesAsArray(): boolean { if (settingsService.settings.useMultipleStatuses) { return true; @@ -141,48 +178,104 @@ export class NoteStatusService extends BaseNoteStatusService { return { storeAsArray: this.shouldStoreStatusesAsArray() }; } + private resolveTargetKeys( + frontmatterTagName: string, + status: StatusIdentifier | NoteStatus, + options?: { includeSourceKey?: boolean }, + ): string[] { + if (!this.isMarkdownFile()) { + return [frontmatterTagName]; + } + + let resolved = resolveFrontmatterKeysForStatus( + status, + settingsService.settings, + { isMarkdownFile: true }, + ); + + const defaultKey = settingsService.settings.tagPrefix; + if ( + resolved.length === 1 && + resolved[0] === defaultKey && + frontmatterTagName !== defaultKey + ) { + resolved = [frontmatterTagName]; + } + + if (options?.includeSourceKey && frontmatterTagName) { + resolved = [...resolved, frontmatterTagName]; + } + + const uniqueKeys = Array.from( + new Set(resolved.filter((key) => key && key.trim().length)), + ); + return uniqueKeys.length ? uniqueKeys : [frontmatterTagName]; + } + + private async runAcrossKeys( + keys: string[], + action: (key: string) => Promise, + ): Promise { + let hasChanged = false; + for (const key of keys) { + const changed = await action(key); + if (changed) { + hasChanged = true; + } + } + return hasChanged; + } + private statusesAreEqual(a: string[], b: string[]): boolean { if (a.length !== b.length) return false; return a.every((value, index) => value === b[index]); } public populateStatuses(): void { - const STATUS_METADATA_KEYS = this.getStatusMetadataKeys(); - this.statuses = Object.fromEntries( - STATUS_METADATA_KEYS.map((key) => [key, []]), - ); + const defaultKey = settingsService.settings.tagPrefix; + this.statuses = { [defaultKey]: [] }; if (!this.file) { return; } - STATUS_METADATA_KEYS.forEach((key) => { - const identifiers = this.statusStore.getStatuses(this.file, key); - if (!identifiers.length) { - return; - } + const identifiers = this.collectIdentifiersForFile(); + if (!identifiers.length) { + return; + } - let statuses = identifiers - .map((identifier) => - this.statusNameToObject(identifier.toString()), - ) - .filter( - (status): status is NoteStatusType => status !== undefined, - ); + let statuses = identifiers + .map((identifier) => this.statusNameToObject(identifier)) + .filter((status): status is NoteStatusType => status !== undefined); - if ( - statuses.length && - !settingsService.settings.useMultipleStatuses - ) { - statuses = statuses.slice(0, 1); - } - this.statuses[key] = statuses; - }); + if (statuses.length && !settingsService.settings.useMultipleStatuses) { + statuses = statuses.slice(0, 1); + } + this.statuses[defaultKey] = statuses; } async removeStatus( frontmatterTagName: string, status: NoteStatus, + ): Promise { + const targetKeys = this.resolveTargetKeys(frontmatterTagName, status, { + includeSourceKey: true, + }); + const removed = await this.runAcrossKeys(targetKeys, (key) => + this.removeStatusFromKey(key, status), + ); + + if (removed) { + eventBus.publish("status-changed", { + file: this.file, + }); + } + return removed; + } + + private async removeStatusFromKey( + frontmatterTagName: string, + status: NoteStatus, ): Promise { const targetIdentifier = status.templateId ? BaseNoteStatusService.formatStatusIdentifier({ @@ -191,7 +284,7 @@ export class NoteStatusService extends BaseNoteStatusService { }) : status.name; - const removed = await this.statusStore.mutateStatuses( + return this.statusStore.mutateStatuses( this.file, frontmatterTagName, (current) => { @@ -219,17 +312,29 @@ export class NoteStatusService extends BaseNoteStatusService { }, this.getStoreOptions(), ); + } - if (removed) { + async clearStatus(frontmatterTagName: string): Promise { + const keys = new Set([frontmatterTagName]); + if (this.isMarkdownFile()) { + this.getKeysForReading().forEach((key) => keys.add(key)); + } + + const cleared = await this.runAcrossKeys([...keys], (key) => + this.clearStatusForKey(key), + ); + if (cleared) { eventBus.publish("status-changed", { file: this.file, }); } - return removed; + return cleared; } - async clearStatus(frontmatterTagName: string): Promise { - const cleared = await this.statusStore.mutateStatuses( + private async clearStatusForKey( + frontmatterTagName: string, + ): Promise { + return this.statusStore.mutateStatuses( this.file, frontmatterTagName, (current) => { @@ -240,37 +345,50 @@ export class NoteStatusService extends BaseNoteStatusService { }, this.getStoreOptions(), ); - if (cleared) { - eventBus.publish("status-changed", { - file: this.file, - }); - } - return cleared; } async overrideStatuses( frontmatterTagName: string, statusIdentifiers: StatusIdentifier[], ): Promise { - const formattedIdentifiers = statusIdentifiers.map((id) => - typeof id === "string" - ? id - : BaseNoteStatusService.formatStatusIdentifier(id), - ); - const statusesToStore = settingsService.settings.useMultipleStatuses - ? formattedIdentifiers - : formattedIdentifiers.slice(0, 1); + const limitedIdentifiers = settingsService.settings.useMultipleStatuses + ? statusIdentifiers + : statusIdentifiers.slice(0, 1); - const overridden = await this.statusStore.mutateStatuses( - this.file, - frontmatterTagName, - (current) => { - if (this.statusesAreEqual(current, statusesToStore)) { - return { hasChanged: false }; + if (!limitedIdentifiers.length) { + return this.clearStatus(frontmatterTagName); + } + + const keyMap = new Map(); + if (!this.isMarkdownFile()) { + keyMap.set(frontmatterTagName, limitedIdentifiers); + } else { + limitedIdentifiers.forEach((identifier) => { + const keys = this.resolveTargetKeys( + frontmatterTagName, + identifier, + ); + keys.forEach((key) => { + const currentList = keyMap.get(key) ?? []; + currentList.push(identifier); + keyMap.set(key, currentList); + }); + }); + + const knownKeys = new Set([ + frontmatterTagName, + ...this.getStatusMetadataKeys(), + ]); + knownKeys.forEach((key) => { + if (!keyMap.has(key)) { + keyMap.set(key, []); } - return { hasChanged: true, nextStatuses: statusesToStore }; - }, - this.getStoreOptions(), + }); + } + + const overridden = await this.runAcrossKeys( + Array.from(keyMap.keys()), + (key) => this.overrideStatusesForKey(key, keyMap.get(key) ?? []), ); if (overridden) { @@ -281,9 +399,55 @@ export class NoteStatusService extends BaseNoteStatusService { return overridden; } + private async overrideStatusesForKey( + frontmatterTagName: string, + statusIdentifiers: StatusIdentifier[], + ): Promise { + const formattedIdentifiers = statusIdentifiers.map((id) => + typeof id === "string" + ? id + : BaseNoteStatusService.formatStatusIdentifier(id), + ); + + return this.statusStore.mutateStatuses( + this.file, + frontmatterTagName, + (current) => { + if (this.statusesAreEqual(current, formattedIdentifiers)) { + return { hasChanged: false }; + } + return { + hasChanged: true, + nextStatuses: formattedIdentifiers, + }; + }, + this.getStoreOptions(), + ); + } + async addStatus( frontmatterTagName: string, statusIdentifier: StatusIdentifier, + ): Promise { + const targetKeys = this.resolveTargetKeys( + frontmatterTagName, + statusIdentifier, + ); + const added = await this.runAcrossKeys(targetKeys, (key) => + this.addStatusToKey(key, statusIdentifier), + ); + + if (added) { + eventBus.publish("status-changed", { + file: this.file, + }); + } + return added; + } + + private async addStatusToKey( + frontmatterTagName: string, + statusIdentifier: StatusIdentifier, ): Promise { const formattedIdentifier = typeof statusIdentifier === "string" @@ -292,7 +456,7 @@ export class NoteStatusService extends BaseNoteStatusService { statusIdentifier, ); - const added = await this.statusStore.mutateStatuses( + return this.statusStore.mutateStatuses( this.file, frontmatterTagName, (current) => { @@ -334,13 +498,6 @@ export class NoteStatusService extends BaseNoteStatusService { }, this.getStoreOptions(), ); - - if (added) { - eventBus.publish("status-changed", { - file: this.file, - }); - } - return added; } } @@ -356,14 +513,20 @@ export class MultipleNoteStatusService extends BaseNoteStatusService { return this.files.length; } + private getKeysForReading(file: TFile): string[] { + const defaultKey = settingsService.settings.tagPrefix; + if (file.extension !== "md") { + return [defaultKey]; + } + const knownKeys = getKnownFrontmatterKeys(settingsService.settings); + return [defaultKey, ...knownKeys.filter((key) => key !== defaultKey)]; + } + public populateStatuses(): void { - const STATUS_METADATA_KEYS = this.getStatusMetadataKeys(); + const defaultKey = settingsService.settings.tagPrefix; + this.statuses = { [defaultKey]: [] }; - this.statuses = Object.fromEntries( - STATUS_METADATA_KEYS.map((key) => [key, []]), - ); - - const allStatuses = new Map>(); + const aggregated = new Set(); this.files.forEach((file) => { let store: StatusStore; @@ -373,30 +536,27 @@ export class MultipleNoteStatusService extends BaseNoteStatusService { return; } - STATUS_METADATA_KEYS.forEach((key) => { + const keys = this.getKeysForReading(file); + keys.forEach((key) => { const identifiers = store.getStatuses(file, key); - if (!identifiers.length) return; - - if (!allStatuses.has(key)) { - allStatuses.set(key, new Set()); - } - - identifiers.forEach((name) => - allStatuses.get(key)!.add(name.toString()), - ); + identifiers.forEach((identifier) => { + const normalized = identifier?.toString(); + if (normalized && !aggregated.has(normalized)) { + aggregated.add(normalized); + } + }); }); }); - STATUS_METADATA_KEYS.forEach((key) => { - const statusNames = allStatuses.get(key); - if (statusNames) { - const statuses = Array.from(statusNames) - .map((name) => this.statusNameToObject(name)) - .filter((s) => s !== undefined) as NoteStatusType[]; + if (!aggregated.size) { + return; + } - this.statuses[key] = statuses; - } - }); + const statuses = Array.from(aggregated) + .map((identifier) => this.statusNameToObject(identifier)) + .filter((status): status is NoteStatusType => status !== undefined); + + this.statuses[defaultKey] = statuses; } async removeStatus( diff --git a/integrations/file-explorer/file-explorer-integration.tsx b/integrations/file-explorer/file-explorer-integration.tsx index aeca623..918179c 100644 --- a/integrations/file-explorer/file-explorer-integration.tsx +++ b/integrations/file-explorer/file-explorer-integration.tsx @@ -68,6 +68,7 @@ export class FileExplorerIntegration implements IElementProcessor { key === "customStatuses" || key === "useMultipleStatuses" || key === "tagPrefix" || + key === "statusFrontmatterMappings" || key === "strictStatuses" || key === "fileExplorerIconPosition" || key === "fileExplorerIconFrame" || diff --git a/integrations/status-bar/status-bar.tsx b/integrations/status-bar/status-bar.tsx index ad98055..2feba05 100644 --- a/integrations/status-bar/status-bar.tsx +++ b/integrations/status-bar/status-bar.tsx @@ -50,6 +50,9 @@ export class StatusBarIntegration { if (key === "tagPrefix") { this.handleActiveFileChange().catch(console.error); // INFO: Force a re-read of the statuses and render } + if (key === "statusFrontmatterMappings") { + this.handleActiveFileChange().catch(console.error); + } if (key === "useCustomStatusesOnly") { this.handleActiveFileChange().catch(console.error); // INFO: Force a re-read of the statuses and render } diff --git a/integrations/toolbar/editorToolbarIntegration.tsx b/integrations/toolbar/editorToolbarIntegration.tsx index 77c7afd..79465b7 100644 --- a/integrations/toolbar/editorToolbarIntegration.tsx +++ b/integrations/toolbar/editorToolbarIntegration.tsx @@ -71,6 +71,9 @@ export class EditorToolbarIntegration { if (key === "tagPrefix") { this.refreshAllButtons(); } + if (key === "statusFrontmatterMappings") { + this.refreshAllButtons(); + } if (key === "useCustomStatusesOnly") { this.refreshAllButtons(); } diff --git a/integrations/views/grouped-dashboard-view.tsx b/integrations/views/grouped-dashboard-view.tsx index 877ef92..015bad1 100644 --- a/integrations/views/grouped-dashboard-view.tsx +++ b/integrations/views/grouped-dashboard-view.tsx @@ -8,6 +8,7 @@ import { import eventBus from "@/core/eventBus"; import settingsService from "@/core/settingsService"; import { NoteStatus } from "@/types/noteStatus"; +import { getKnownFrontmatterKeys } from "@/utils/frontmatterMappings"; export const VIEW_TYPE_GROUPED_DASHBOARD = "grouped-dashboard-view"; @@ -71,7 +72,9 @@ export class GroupedDashboardView extends ItemView { private processFiles = (files: FileItem[]): GroupedByStatus => { const result: GroupedByStatus = {}; - const statusMetadataKeys = [settingsService.settings.tagPrefix]; + const statusMetadataKeys = getKnownFrontmatterKeys( + settingsService.settings, + ); const availableStatuses = BaseNoteStatusService.getAllAvailableStatuses(); @@ -147,6 +150,7 @@ export class GroupedDashboardView extends ItemView { key === "useCustomStatusesOnly" || key === "customStatuses" || key === "useMultipleStatuses" || + key === "statusFrontmatterMappings" || key === "strictStatuses" ) { onDataChange(); diff --git a/integrations/views/grouped-status-view.tsx b/integrations/views/grouped-status-view.tsx index 5e94a54..3596a50 100644 --- a/integrations/views/grouped-status-view.tsx +++ b/integrations/views/grouped-status-view.tsx @@ -13,6 +13,7 @@ import { import eventBus from "@/core/eventBus"; import settingsService from "@/core/settingsService"; import { NoteStatus } from "@/types/noteStatus"; +import { getKnownFrontmatterKeys } from "@/utils/frontmatterMappings"; export const VIEW_TYPE_EXAMPLE = "grouped-status-view"; @@ -55,7 +56,9 @@ export class GroupedStatusView extends ItemView { private processFiles = (files: FileItem[]): GroupedByStatus => { const result: GroupedByStatus = {}; - const statusMetadataKeys = [settingsService.settings.tagPrefix]; + const statusMetadataKeys = getKnownFrontmatterKeys( + settingsService.settings, + ); const availableStatuses = BaseNoteStatusService.getAllAvailableStatuses(); @@ -131,6 +134,7 @@ export class GroupedStatusView extends ItemView { key === "useCustomStatusesOnly" || key === "customStatuses" || key === "useMultipleStatuses" || + key === "statusFrontmatterMappings" || key === "strictStatuses" || key === "vaultSizeLimit" ) { diff --git a/integrations/views/status-dashboard-view.tsx b/integrations/views/status-dashboard-view.tsx index d503a43..7709151 100644 --- a/integrations/views/status-dashboard-view.tsx +++ b/integrations/views/status-dashboard-view.tsx @@ -10,6 +10,7 @@ import settingsService from "@/core/settingsService"; import eventBus from "@/core/eventBus"; import { VaultStats } from "@/components/StatusDashboard/useVaultStats"; import { NoteStatus } from "@/types/noteStatus"; +import { getKnownFrontmatterKeys } from "@/utils/frontmatterMappings"; interface AppWithCommands extends App { commands: { @@ -61,7 +62,9 @@ export class StatusDashboardView extends ItemView { const files = this.app.vault.getFiles(); const availableStatuses = BaseNoteStatusService.getAllAvailableStatuses(); - const statusMetadataKeys = [settingsService.settings.tagPrefix]; + const statusMetadataKeys = getKnownFrontmatterKeys( + settingsService.settings, + ); let notesWithStatus = 0; const statusDistribution: Record = {}; @@ -224,12 +227,19 @@ export class StatusDashboardView extends ItemView { private findUnassignedNotes() { const files = this.app.vault.getMarkdownFiles(); + const statusKeys = getKnownFrontmatterKeys(settingsService.settings); const filesWithoutStatus = files.filter((file) => { const cachedMetadata = this.app.metadataCache.getFileCache(file); const frontmatter = cachedMetadata?.frontmatter; - return ( - !frontmatter || !frontmatter[settingsService.settings.tagPrefix] - ); + if (!frontmatter) return true; + + return !statusKeys.some((key) => { + const value = frontmatter[key]; + if (Array.isArray(value)) { + return value.length > 0; + } + return Boolean(value); + }); }); if (filesWithoutStatus.length === 0) { @@ -242,8 +252,7 @@ export class StatusDashboardView extends ItemView { ); // Create a search query to find files without the status tag - const tagPrefix = settingsService.settings.tagPrefix; - const query = `-[${tagPrefix}:]`; + const query = statusKeys.map((key) => `-[${key}:]`).join(" "); // @ts-ignore this.app.internalPlugins.plugins[ @@ -252,8 +261,10 @@ export class StatusDashboardView extends ItemView { } private searchBySpecificStatus(statusName: string) { - const tagPrefix = settingsService.settings.tagPrefix; - const query = `[${tagPrefix}:"${statusName}"]`; + const keys = getKnownFrontmatterKeys(settingsService.settings); + const query = keys + .map((key) => `[${key}:"${statusName}"]`) + .join(" OR "); // @ts-ignore this.app.internalPlugins.plugins[ @@ -325,6 +336,7 @@ export class StatusDashboardView extends ItemView { key === "customStatuses" || key === "useMultipleStatuses" || key === "strictStatuses" || + key === "statusFrontmatterMappings" || key === "vaultSizeLimit" ) { handleVaultChange(); diff --git a/styles.css b/styles.css index db7ae32..21696c4 100644 --- a/styles.css +++ b/styles.css @@ -1 +1 @@ -.status-bar-enable-button{cursor:pointer;opacity:var(--icon-opacity);transition:opacity var(--anim-duration-fast) ease}.status-bar-enable-button:hover{opacity:var(--icon-opacity-hover)}.status-bar-group-row{display:flex;align-items:center;gap:var(--size-4-2)}.status-bar-group-container{display:flex;gap:var(--size-2-1);flex-wrap:wrap;align-items:center}@keyframes status-slide-in{0%{opacity:0;transform:scale(.8) translateY(4px)}to{opacity:1;transform:scale(1) translateY(0)}}.setting-item-full{width:100%}.setting-item-vertical{flex-direction:column;align-items:flex-start;gap:var(--size-2-2)}.custom-status-item{background:var(--background-secondary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);padding:var(--size-4-3);margin-bottom:var(--size-4-2);transition:all var(--anim-duration-fast) ease}.custom-status-item:hover{border-color:var(--interactive-hover)}.custom-status-item__row{display:flex;align-items:end;gap:var(--size-4-2);margin-bottom:var(--size-4-2)}.custom-status-item__row--icons{align-items:stretch;flex-wrap:wrap}.custom-status-item__field{display:flex;flex-direction:column;gap:var(--size-2-1)}.custom-status-item__field--icon,.custom-status-item__field--lucide{flex:1;min-width:240px}.custom-status-item__field--name,.custom-status-item__field--description{flex:2;min-width:120px}.custom-status-item__field--color{flex:0 0 auto}.custom-status-item__field--reorder,.custom-status-item__field--actions{flex:0 0 auto;padding-top:var(--size-4-1)}.custom-status-item__label{font-size:var(--font-ui-smaller);font-weight:var(--font-medium);color:var(--text-muted);margin-bottom:var(--size-2-1)}.custom-status-item__label--required{color:var(--text-error)}.custom-status-item__input--icon{width:100%!important;text-align:left;font-size:1em}.custom-status-item__hint{margin:0;font-size:var(--font-ui-smaller);color:var(--text-muted)}.custom-status-item__input--name{font-weight:var(--font-medium)}.custom-status-item__input--name.custom-status-item__input--invalid{border-color:var(--text-error)!important;background:var(--background-modifier-error)!important}.custom-status-item__input--description{color:var(--text-muted)}.custom-status-item__input--color{width:40px!important;height:32px!important;border-radius:var(--radius-s);cursor:pointer;border:2px solid var(--background-modifier-border)!important}.custom-status-item__input--color:hover{border-color:var(--interactive-hover)!important}.custom-status-item__remove-btn{width:32px;height:32px;border-radius:var(--radius-s);background:var(--background-modifier-error);color:var(--text-on-accent);border:none;cursor:pointer;font-size:18px;font-weight:700;transition:all var(--anim-duration-fast) ease}.custom-status-item__remove-btn:hover{background:var(--text-error);transform:scale(1.05)}.custom-status-item__reorder-buttons{display:flex;flex-direction:column;gap:2px}.custom-status-item__reorder-btn{width:24px;height:24px;border-radius:var(--radius-s);background:var(--background-modifier-border);color:var(--text-normal);border:none;cursor:pointer;font-size:12px;font-weight:700;transition:all var(--anim-duration-fast) ease;display:flex;align-items:center;justify-content:center;line-height:1}.custom-status-item__reorder-btn:hover:not(:disabled){background:var(--interactive-hover);transform:scale(1.05)}.custom-status-item__reorder-btn:disabled{opacity:.5;cursor:not-allowed}.custom-status-item__preview{display:flex;align-items:center;gap:var(--size-2-2);padding:var(--size-2-3) var(--size-4-2);background:var(--background-primary-alt);border-radius:var(--radius-s);border-left:3px solid var(--background-modifier-border);font-size:var(--font-ui-small)}.custom-status-item__preview-icon{font-size:1.1em;min-width:20px;text-align:center}.custom-status-item__preview-text{font-weight:var(--font-medium)}.custom-status-item__preview-desc{color:var(--text-muted);font-style:italic}.custom-status-item__preview-desc:before{content:"\2014 "}.custom-status-item__error{color:var(--text-error);font-size:var(--font-ui-smaller);margin-top:var(--size-2-1);padding:var(--size-2-1) var(--size-2-2);background:var(--background-modifier-error);border-radius:var(--radius-s);border-left:3px solid var(--text-error)}.custom-status-list__empty{text-align:center;padding:var(--size-4-4) var(--size-4-2);color:var(--text-muted);font-style:italic;border:2px dashed var(--background-modifier-border);border-radius:var(--radius-m);background:var(--background-primary-alt)}.custom-status-list__empty p{margin:0}.status-group{margin-bottom:var(--size-4-4);padding:var(--size-4-3);background:var(--background-primary-alt);border-radius:var(--radius-m);border:1px solid var(--background-modifier-border)}.status-group__header{margin-bottom:var(--size-4-2);padding-bottom:var(--size-2-2);border-bottom:1px solid var(--background-modifier-border)}.status-group__title{font-weight:var(--font-semibold);color:var(--text-normal);font-size:var(--font-ui-medium)}.status-group__description{font-size:var(--font-ui-smaller);color:var(--text-muted);margin-top:var(--size-2-1);line-height:var(--line-height-tight)}.status-group__items{display:flex;flex-direction:column;gap:var(--size-2-2)}.status-selector{display:flex;align-items:center;justify-content:space-between;padding:var(--size-2-3) var(--size-4-2);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background:var(--background-primary);cursor:pointer;transition:all var(--anim-duration-fast) ease;position:relative}.status-selector:hover{background:var(--background-modifier-hover);border-color:var(--background-modifier-border-hover)}.status-selector--selected{background:var(--background-modifier-success);border-color:var(--interactive-success)}.status-selector--selected:hover{background:var(--background-modifier-success-hover)}.status-selector__content{flex:1;min-width:0}.status-selector__status{display:flex;align-items:center;gap:var(--size-2-2);margin-bottom:var(--size-2-1)}.status-selector__icon{font-size:var(--font-ui-medium);width:var(--size-4-3);text-align:center;flex-shrink:0}.status-selector__name{font-weight:var(--font-medium);color:var(--text-normal)}.status-selector__description{font-size:var(--font-ui-smaller);color:var(--text-muted);line-height:var(--line-height-tight)}.status-selector__checkbox{margin-left:var(--size-4-2);pointer-events:none;flex-shrink:0}.quick-commands-container{margin-top:var(--size-4-2)}.note-status-options{max-height:300px;overflow-y:auto;border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--background-primary)}.note-status-option{align-items:stretch}.note-status-option .selectable-list-item-icon{display:flex;align-items:center;justify-content:center}.note-status-option__icon-wrapper{display:inline-flex;align-items:center;justify-content:center;padding:var(--size-2-1);border-radius:var(--radius-m);border:1px solid var(--background-modifier-border);background:var(--background-secondary);min-width:36px;transition:border-color var(--anim-duration-fast) ease,background var(--anim-duration-fast) ease}.note-status-option:hover .note-status-option__icon-wrapper,.note-status-option.selectable-list-item[data-selected=true] .note-status-option__icon-wrapper{border-color:var(--interactive-accent);background:var(--background-modifier-hover)}.note-status-option__text{display:flex;flex-direction:column;gap:2px}.note-status-option__name{font-weight:var(--font-medium);color:var(--text-normal)}.note-status-option__description{font-size:var(--font-ui-smaller);color:var(--text-muted)}.note-status-chips{display:flex;flex-wrap:wrap;gap:var(--size-2-2);min-height:32px;align-items:center}.selectable-list-item{display:flex;align-items:center;gap:var(--size-4-2);padding:var(--size-2-3) var(--size-4-2);cursor:pointer;border-bottom:1px solid var(--background-modifier-border);transition:background var(--anim-duration-fast) ease}.selectable-list-item:hover{background:var(--background-modifier-hover)}.selectable-list-item[data-focused=true]{outline:2px solid var(--interactive-accent);outline-offset:-2px}.selectable-list-item[data-selected=true]{background:var(--background-modifier-hover)}.selectable-list-item-icon{font-size:16px;min-width:20px}.selectable-list-item-content{flex:1;font-size:var(--font-ui-small)}.selectable-list-item-check{color:var(--interactive-accent)}.template-settings-actions{display:flex;justify-content:end;gap:var(--size-4-2);padding-bottom:var(--size-4-2)}.template-create-btn{display:flex;align-items:center;gap:var(--size-2-2);padding:var(--size-2-3) var(--size-4-3);font-weight:var(--font-medium);font-size:var(--font-ui-medium)}.template-reset-btn{display:flex;align-items:center;gap:var(--size-2-2);padding:var(--size-2-3) var(--size-4-3);background:var(--interactive-normal);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);color:var(--text-normal);cursor:pointer;font-size:var(--font-ui-medium);transition:all var(--anim-duration-fast) ease}.template-reset-btn:hover{background:var(--interactive-hover);color:var(--text-normal)}.template-section{margin-bottom:var(--size-4-6)}.template-section-title{display:flex;align-items:center;gap:var(--size-2-2);font-size:var(--font-ui-large);font-weight:var(--font-semibold);color:var(--text-normal);margin-bottom:var(--size-4-3);padding-bottom:var(--size-2-2);border-bottom:2px solid var(--background-modifier-border)}.template-section-title .lucide{opacity:.8;color:var(--text-muted)}.template-list{display:flex;flex-direction:column;gap:var(--size-4-2)}.template-item{position:relative;display:flex;align-items:stretch;border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background:var(--background-primary-alt);transition:all var(--anim-duration-fast) ease;overflow:hidden}.template-item:hover{background:var(--background-modifier-hover);border-color:var(--background-modifier-border-hover);box-shadow:0 2px 8px var(--shadow-color)}.template-item.enabled{background:var(--background-modifier-hover);border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.template-item.enabled:hover{box-shadow:0 2px 8px var(--shadow-color),0 0 0 1px var(--interactive-accent)}.template-item-content{flex:1;border:none!important;border-radius:0!important;background:transparent!important}.template-item-main{flex:1}.template-header{display:flex;align-items:center;gap:var(--size-2-2);margin-bottom:var(--size-2-1)}.template-custom-badge{display:flex;align-items:center;gap:var(--size-2-1);padding:var(--size-2-1) var(--size-2-2);background:var(--interactive-accent);color:var(--text-on-accent);font-size:var(--font-ui-smaller);font-weight:var(--font-medium);border-radius:var(--radius-s);text-transform:uppercase;letter-spacing:.05em}.template-item-actions{display:flex;flex-direction:column;padding:var(--size-4-2);gap:var(--size-2-1);background:var(--background-modifier-border);border-left:1px solid var(--background-modifier-border-hover);min-width:44px;align-items:center;justify-content:center}.template-editor-modal{background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-l);box-shadow:var(--shadow-l);max-width:800px;width:100%}.template-editor-modal__header{padding:var(--size-4-4) var(--size-4-6);background:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border);border-radius:var(--radius-l) var(--radius-l) 0 0}.template-editor-modal__header h2{margin:0;font-size:var(--font-ui-larger);font-weight:var(--font-bold);color:var(--text-normal)}.template-editor-modal__content{padding:var(--size-4-6);max-height:70vh;overflow-y:auto}.template-editor-statuses{display:flex;flex-direction:column;gap:var(--size-4-3)}.template-editor-add-status{display:flex;align-items:center;gap:var(--size-2-2);align-self:flex-start;margin-top:var(--size-4-2)}.template-editor-add-status .lucide{width:16px;height:16px}.template-editor-modal__actions{display:flex;gap:var(--size-4-2);justify-content:flex-end;padding:var(--size-4-4) var(--size-4-6);background:var(--background-secondary);border-top:1px solid var(--background-modifier-border);border-radius:0 0 var(--radius-l) var(--radius-l)}.template-editor-modal__actions button{padding:var(--size-2-3) var(--size-4-4);border-radius:var(--radius-m);font-size:var(--font-ui-medium);font-weight:var(--font-medium);cursor:pointer;transition:all var(--anim-duration-fast) ease;border:1px solid var(--background-modifier-border)}.template-editor-modal__actions button:not(.mod-cta){background:var(--interactive-normal);color:var(--text-normal)}.template-editor-modal__actions button:not(.mod-cta):hover{background:var(--interactive-hover)}.template-editor-modal__actions .mod-cta:disabled{opacity:.5;cursor:not-allowed;transform:none}.template-editor-modal__actions .mod-cta:disabled:hover{transform:none}@media (max-width: 768px){.template-settings-actions,.template-item{flex-direction:column}.template-item-actions{flex-direction:row;border-left:none;border-top:1px solid var(--background-modifier-border-hover);min-width:unset;padding:var(--size-2-3) var(--size-4-2)}.template-editor-modal{margin:var(--size-4-2);max-width:calc(100% - var(--size-4-4))}}.template-editor-modal__content::-webkit-scrollbar{width:var(--scrollbar-width)}.template-editor-modal__content::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb-bg);border-radius:var(--radius-s)}.template-editor-modal__content::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-active-bg)}.status-dashboard-view-container{height:100%;overflow:hidden}.status-dashboard{background:var(--background-primary)}.status-dashboard-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-3) var(--size-4-4);background:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border)}.status-dashboard-title{margin:0;font-size:var(--font-ui-larger);font-weight:var(--font-bold)}.status-dashboard-refresh{padding:var(--size-2-2) var(--size-2-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--interactive-normal);color:var(--text-normal);cursor:pointer;font-size:var(--font-ui-medium);transition:all var(--anim-duration-fast) ease}.status-dashboard-refresh:hover{background:var(--interactive-hover);transform:rotate(90deg)}.status-dashboard-content{flex:1;overflow-y:auto;padding:var(--size-4-3);display:flex;flex-direction:column;gap:var(--size-4-4)}.status-dashboard-section{background:var(--background-secondary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);overflow:hidden}.status-dashboard-section-header{padding:var(--size-4-2) var(--size-4-3);background:var(--background-modifier-hover);border-bottom:1px solid var(--background-modifier-border)}.status-dashboard-section-header h3{margin:0;font-size:var(--font-ui-large);font-weight:var(--font-semibold)}.status-dashboard-current-note{padding:var(--size-4-3)}.current-note-info{margin-bottom:var(--size-4-3);padding-bottom:var(--size-4-2);border-bottom:1px solid var(--background-modifier-border)}.current-note-name{font-size:var(--font-ui-large);font-weight:var(--font-semibold);margin-bottom:var(--size-2-1)}.current-note-path{font-size:var(--font-ui-small);color:var(--text-muted);font-family:var(--font-monospace);margin-bottom:var(--size-2-1)}.current-note-modified{font-size:var(--font-ui-smaller);color:var(--text-faint);font-style:italic}.current-note-statuses{display:flex;flex-direction:column;gap:var(--size-4-2)}.current-note-tag-group{display:flex;flex-direction:column;gap:var(--size-2-2)}.current-note-status-list{display:flex;flex-wrap:wrap;gap:var(--size-2-2)}.current-note-no-status{font-size:var(--font-ui-small);color:var(--text-muted);font-style:italic;padding:var(--size-2-1) var(--size-2-3);background:var(--background-modifier-border);border-radius:var(--radius-s)}.current-note-empty{text-align:center;color:var(--text-muted);font-style:italic;padding:var(--size-4-4)}.vault-overview{padding:var(--size-4-3)}.vault-stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:var(--size-4-2)}.vault-stat-card{text-align:center;padding:var(--size-4-2);background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);transition:all var(--anim-duration-fast) ease}.vault-stat-card:hover{background:var(--background-modifier-hover);transform:translateY(-2px);box-shadow:var(--shadow-s)}.vault-stat-number{font-size:var(--font-ui-largest);font-weight:var(--font-bold);color:var(--interactive-accent);margin-bottom:var(--size-2-1)}.vault-stat-label{font-size:var(--font-ui-small);color:var(--text-muted);font-weight:var(--font-medium);text-transform:uppercase;letter-spacing:.05em}.status-distribution{padding:var(--size-4-3)}.status-chart{display:flex;flex-direction:column;gap:var(--size-4-2)}.status-chart-item{display:flex;flex-direction:column;gap:var(--size-2-1)}.status-chart-item--clickable{cursor:pointer;transition:all var(--anim-duration-fast) ease;padding:var(--size-2-2);border-radius:var(--radius-s)}.status-chart-item--clickable:hover{background:var(--background-modifier-hover);transform:translateY(-1px)}.status-chart-info{display:flex;align-items:center;gap:var(--size-2-3)}.status-chart-count{font-size:var(--font-ui-small);color:var(--text-muted);font-weight:var(--font-medium)}.status-chart-bar{height:8px;background:var(--background-modifier-border);border-radius:var(--radius-s);overflow:hidden}.status-chart-fill{height:100%;transition:width var(--anim-duration-moderate) ease}.status-distribution-empty{text-align:center;color:var(--text-muted);font-style:italic;padding:var(--size-4-4)}.quick-actions{padding:var(--size-4-3);display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:var(--size-4-3)}.quick-actions-group{display:flex;flex-direction:column;gap:var(--size-2-2)}.quick-actions-group-title{font-size:var(--font-ui-small);font-weight:var(--font-semibold);color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;padding-bottom:var(--size-2-1);border-bottom:1px solid var(--background-modifier-border);margin-bottom:var(--size-2-1)}.quick-action-btn{width:100%;padding:var(--size-2-3) var(--size-4-2);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background:var(--interactive-normal);color:var(--text-normal);cursor:pointer;font-size:var(--font-ui-medium);font-weight:var(--font-medium);text-align:left;display:flex;align-items:center;gap:var(--size-2-2);transition:all var(--anim-duration-fast) ease}.quick-action-btn:hover:not(:disabled){background:var(--interactive-hover);transform:translateY(-1px);box-shadow:var(--shadow-s)}.quick-action-btn:disabled{opacity:.5;cursor:not-allowed;background:var(--background-modifier-border);color:var(--text-faint)}@media (max-width: 768px){.quick-actions{grid-template-columns:1fr}}.status-dashboard-loading{display:flex;align-items:center;justify-content:center;height:100%;font-size:var(--font-ui-medium);color:var(--text-muted)}.status-dashboard-disabled{display:flex;align-items:center;justify-content:center;height:100%;padding:var(--size-4-4);text-align:center;font-size:var(--font-ui-medium);color:var(--text-muted);background:var(--background-secondary);border:2px dashed var(--background-modifier-border);border-radius:var(--radius-m);margin:var(--size-4-4)}.status-dashboard-content::-webkit-scrollbar{width:var(--scrollbar-width)}.status-dashboard-content::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb-bg);border-radius:var(--radius-s)}.status-dashboard-content::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-active-bg)}.grouped-status-view-container{height:100%;overflow:hidden}.grouped-status-header{padding:var(--size-4-2) var(--size-4-3);background:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border)}.grouped-status-title{margin:0 0 var(--size-4-2) 0;font-size:var(--font-ui-large);font-weight:var(--font-semibold)}.grouped-status-filters{display:flex;flex-direction:column;gap:var(--size-4-2)}.grouped-status-note-input{width:100%;padding:var(--size-2-2) var(--size-2-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--background-primary);color:var(--text-normal);font-size:var(--font-ui-medium)}.grouped-status-note-input:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--background-modifier-accent)}.grouped-status-content{flex:1;overflow-y:auto;padding:var(--size-4-2)}.grouped-status-tag-section{margin-bottom:var(--size-4-3)}.grouped-status-tag-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-3);background:var(--background-secondary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);cursor:pointer;transition:all var(--anim-duration-fast) ease;margin-bottom:var(--size-4-2)}.grouped-status-tag-header:hover{background:var(--background-modifier-hover)}.grouped-status-tag-content{padding-left:var(--size-4-2)}.grouped-status-group{margin-bottom:var(--size-4-2);background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);overflow:hidden;box-shadow:0 1px 3px #0000001a;transition:box-shadow var(--anim-duration-fast) ease}.grouped-status-group:hover{box-shadow:0 2px 8px #00000026}.grouped-status-group__header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-3);cursor:pointer;transition:background var(--anim-duration-fast) ease;position:relative}.grouped-status-group__header:hover{background:var(--background-modifier-hover)}.grouped-status-group__header:focus{outline:2px solid var(--color-accent);outline-offset:-2px}.grouped-status-group__status{display:flex;align-items:center;gap:var(--size-2-2);min-width:0;flex:1}.grouped-status-group__template-badge{display:inline-flex;align-items:center;padding:var(--size-2-1) var(--size-2-2);background:var(--background-modifier-border);border-radius:var(--radius-s);font-size:var(--font-ui-smaller);color:var(--text-muted);font-weight:var(--font-medium)}.grouped-status-group__info{display:flex;align-items:center;gap:var(--size-2-2);margin-left:auto}.grouped-status-files{background:var(--background-secondary);border-top:1px solid var(--background-modifier-border)}.grouped-status-files-list{max-height:300px;overflow-y:auto}.grouped-status-file-item{padding:var(--size-2-3) var(--size-4-3);cursor:pointer;transition:background var(--anim-duration-fast) ease;border-bottom:1px solid var(--background-modifier-border-hover)}.grouped-status-file-item:hover{background:var(--background-modifier-hover)}.grouped-status-file-item:last-child{border-bottom:none}.grouped-status-file-name{font-weight:var(--font-medium);color:var(--text-normal);margin-bottom:2px}.grouped-status-file-path{font-size:var(--font-ui-small);color:var(--text-muted);opacity:.8}.grouped-status-load-more{display:flex;justify-content:center;padding:var(--size-4-2);border-top:1px solid var(--background-modifier-border)}.grouped-status-load-btn{padding:var(--size-2-2) var(--size-4-2);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--interactive-normal);color:var(--text-normal);cursor:pointer;font-size:var(--font-ui-small);font-weight:var(--font-medium);transition:all var(--anim-duration-fast) ease}.grouped-status-load-btn:hover{background:var(--interactive-hover);transform:translateY(-1px)}.grouped-status-loading,.grouped-status-empty{display:flex;align-items:center;justify-content:center;padding:var(--size-4-6);color:var(--text-muted);font-size:var(--font-ui-medium)}.grouped-status-view-disabled{display:flex;align-items:center;justify-content:center;height:100%;padding:var(--size-4-4);text-align:center;font-size:var(--font-ui-medium);color:var(--text-muted);background:var(--background-secondary);border:2px dashed var(--background-modifier-border);border-radius:var(--radius-m);margin:var(--size-4-4)}.grouped-status-content::-webkit-scrollbar,.grouped-status-files-list::-webkit-scrollbar{width:var(--scrollbar-width)}.grouped-status-content::-webkit-scrollbar-thumb,.grouped-status-files-list::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb-bg);border-radius:var(--radius-s)}.grouped-status-content::-webkit-scrollbar-thumb:hover,.grouped-status-files-list::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-active-bg)}.grouped-status-filters__template{display:flex;flex-direction:column;gap:var(--size-2-1)}.grouped-status-filters__template-select{width:100%;padding:var(--size-2-2) var(--size-2-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--background-primary);color:var(--text-normal);font-size:var(--font-ui-small);cursor:pointer}.grouped-status-filters__template-select:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--background-modifier-accent)}.grouped-status-filters__note{display:flex;flex-direction:column;gap:var(--size-2-1)}.grouped-status-filters__note-input{width:100%;padding:var(--size-2-2) var(--size-2-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--background-primary);color:var(--text-normal);font-size:var(--font-ui-small)}.grouped-status-filters__note-input:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--background-modifier-accent)}.grouped-status-group__status{display:flex;align-items:center;gap:var(--size-2-2)}.grouped-status-group__template-badge{background:var(--background-modifier-border);color:var(--text-muted);padding:var(--size-2-1) var(--size-2-2);border-radius:var(--radius-s);font-size:var(--font-ui-smaller);font-weight:var(--font-medium);text-transform:capitalize;white-space:nowrap}.custom-icon{display:inline-flex}.custom-icon__absolute-right{position:absolute;right:0}.note-status-colored-block{position:relative;border-radius:var(--radius-s);z-index:0}.note-status-colored-block:before{content:"";position:absolute;inset:0;border-radius:inherit;background-color:var(--note-status-block-color, transparent);opacity:.18;pointer-events:none;z-index:-1;transition:background-color var(--anim-duration-fast) ease,opacity var(--anim-duration-fast) ease}.note-status-border-left{position:relative}.note-status-border-left:after{content:"";position:absolute;left:0;top:2px;bottom:2px;width:3px;border-radius:var(--radius-s);background-color:var(--note-status-border-color, transparent);opacity:.35;pointer-events:none}.note-status-underline{text-decoration-line:underline;text-decoration-thickness:2px;text-decoration-color:var(--note-status-underline-color, currentColor);text-underline-offset:3px}.note-status-dot-badge{width:6px;height:6px;border-radius:50%;margin-left:var(--size-2-1);display:inline-flex;align-items:center;justify-content:center;box-shadow:0 0 0 1px var(--background-modifier-border);flex-shrink:0}.status-wrapper{position:relative;display:inline-block}.status-minimal{display:inline-flex;align-items:center;gap:2px;margin-left:var(--size-2-2);padding:2px;border-radius:var(--radius-s);cursor:pointer;opacity:.7;transition:all var(--anim-duration-fast) ease}.status-minimal:hover{opacity:1;background:var(--background-modifier-hover);transform:scale(1.05)}.status-minimal--compact{margin-left:0;padding:0;background:transparent;gap:var(--size-2-1)}.status-minimal--compact .status-minimal__icon{font-size:var(--font-ui-medium)}.status-minimal[data-status-color] .status-minimal__count{background:var(--interactive-accent)}.status-minimal__icon{font-size:11px;line-height:1;display:inline-flex;align-items:center;justify-content:center;filter:grayscale(.2);transition:filter var(--anim-duration-fast) ease}.status-minimal:hover .status-minimal__icon{filter:grayscale(0)}.status-minimal__count{min-width:14px;height:14px;padding:0 3px;background:var(--interactive-accent);color:var(--text-on-accent);font-size:var(--font-ui-smaller);font-weight:var(--font-semibold);border-radius:var(--radius-m);display:flex;align-items:center;justify-content:center}.status-info-popup{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:var(--layer-popover);background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);box-shadow:var(--shadow-l);min-width:280px;max-width:400px;overflow:hidden;pointer-events:none}.status-popup-header{display:flex;align-items:center;gap:var(--size-2-2);padding:var(--size-4-2) var(--size-4-3);background:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border);font-weight:var(--font-semibold);font-size:var(--font-ui-small)}.status-popup-content{padding:var(--size-4-2)}.status-popup-empty{padding:var(--size-4-4);text-align:center;color:var(--text-muted);font-style:italic}.status-group{margin-bottom:var(--size-4-2)}.status-group:last-child{margin-bottom:0}.status-group__header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-2-2) var(--size-2-3);background:var(--background-secondary-alt);border-radius:var(--radius-s);margin-bottom:var(--size-2-2);border:1px solid var(--background-modifier-border);transition:background var(--anim-duration-fast) ease}.status-group__header:hover{background:var(--background-modifier-hover)}.status-group__name{text-transform:capitalize;font-weight:var(--font-medium);font-size:var(--font-ui-smaller);color:var(--text-normal)}.status-group__count{display:inline-flex;align-items:center;padding:var(--size-2-1) var(--size-2-2);background:var(--background-modifier-border);border-radius:var(--radius-s);font-size:var(--font-ui-smaller);color:var(--text-muted);font-weight:var(--font-medium);min-width:20px;justify-content:center}.status-group__items{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:var(--size-2-2);padding:0 var(--size-2-1)}.status-item{display:flex;flex-direction:column;gap:var(--size-2-1);padding:var(--size-2-2);background:var(--background-primary-alt);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s)}.status-description{font-size:var(--font-ui-smaller);color:var(--text-muted);line-height:1.4}.group-label-container{display:flex;align-items:center;gap:var(--size-2-2);font-size:var(--font-ui-small);font-weight:var(--font-semibold);text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);transition:color var(--anim-duration-fast) ease}.group-label-container--highlighted{color:var(--text-normal)}.group-label-container svg{width:16px;height:16px;opacity:.8}.collapsible-counter-container{padding:var(--size-2-1) var(--size-2-3);border-radius:var(--radius-s);background:var(--background-modifier-border);border:1px solid var(--background-modifier-border-hover);color:var(--text-muted);font-weight:var(--font-medium);font-size:var(--font-ui-smaller);cursor:pointer;transition:all var(--anim-duration-fast) ease;min-width:28px;text-align:center}.collapsible-counter-container:hover{background:var(--background-modifier-border-hover);color:var(--text-normal)}.note-status-chip{display:inline-flex;align-items:center;gap:var(--size-2-2);padding:var(--size-2-1) var(--size-2-3);background:var(--interactive-accent);color:var(--text-on-accent);border-radius:var(--radius-s);font-size:var(--font-ui-smaller);cursor:pointer;transition:all var(--anim-duration-fast) ease}.note-status-chip:hover{opacity:.9;transform:translateY(-1px)}.note-status-chip--removing{opacity:.5;transform:scale(.95)}.note-status-chip__icon{font-size:1em}.note-status-chip__text{font-weight:var(--font-medium)}.note-status-chip__remove{display:flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;background:rgba(255,255,255,.2);cursor:pointer;margin-left:var(--size-2-1)}.note-status-chip__remove:hover{background:rgba(255,255,255,.3)}.status-badge-container{display:inline-flex;align-items:center;padding:var(--size-2-1) var(--size-2-3);border-radius:var(--radius-s);transition:all var(--anim-duration-fast) ease;cursor:pointer}.status-badge-item{display:flex;align-items:center;gap:var(--size-2-1)}.status-badge-icon{font-size:var(--font-ui-small)}.status-badge-text{font-weight:var(--font-medium);font-size:var(--font-ui-small)}.template-status-chip{display:inline-flex;align-items:center;gap:var(--size-2-1);padding:var(--size-2-1) var(--size-2-3);background:var(--background-secondary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);font-size:var(--font-ui-smaller)}.template-status-color-dot{width:8px;height:8px;border-radius:50%;background-color:var(--dot-color)}.lucide-icon-picker{position:relative;display:flex;flex-direction:column;gap:var(--size-2-1)}.lucide-icon-picker__text-input{max-width:240px}.lucide-icon-picker__row{display:flex;gap:var(--size-2-1);align-items:center}.lucide-icon-picker__modal-button{display:inline-flex;align-items:center;gap:var(--size-2-1);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);padding:var(--size-2-1) var(--size-4-1);background:var(--background-secondary);cursor:pointer;transition:border var(--anim-duration-fast) ease,background var(--anim-duration-fast) ease;min-width:220px;justify-content:space-between}.lucide-icon-picker__modal-button:hover{border-color:var(--interactive-accent)}.lucide-icon-picker__preview{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px}.lucide-icon-picker__label{flex:1;text-align:left;font-size:var(--font-ui-smaller);color:var(--text-muted)}.lucide-icon-picker__clear-trigger{background:transparent;border:none;color:var(--text-muted);font-size:var(--font-ui-smaller);cursor:pointer;padding:var(--size-2-1) var(--size-2-2)}.lucide-icon-picker__clear-trigger:hover{color:var(--text-normal);text-decoration:underline}.lucide-icon-modal-wrapper{padding:var(--size-4-2)}.lucide-icon-modal{display:flex;flex-direction:column;gap:var(--size-2-1)}.lucide-icon-modal__header{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--size-2-2)}.lucide-icon-modal__count{margin:0;font-size:var(--font-ui-smaller);color:var(--text-muted)}.lucide-icon-modal__close{display:inline-flex;align-items:center;justify-content:center}.lucide-icon-modal__search{width:100%}.lucide-icon-modal__toggle{display:inline-flex;align-items:center;gap:var(--size-2-1);font-size:var(--font-ui-smaller);color:var(--text-muted)}.lucide-icon-modal__list{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:var(--size-2-1);max-height:360px;overflow-y:auto;padding-right:var(--size-2-1)}.lucide-icon-modal__list--icons-only{grid-template-columns:repeat(auto-fill,minmax(70px,1fr))}.lucide-icon-modal__option{display:flex;align-items:center;gap:var(--size-2-1);padding:var(--size-2-1);border-radius:var(--radius-s);border:1px solid var(--background-modifier-border);background:var(--background-secondary);cursor:pointer;font-size:var(--font-ui-smaller);justify-content:flex-start}.lucide-icon-modal__option:hover{background:var(--background-modifier-hover)}.lucide-icon-modal__option--selected{border-color:var(--interactive-accent);background:var(--background-modifier-success)}.lucide-icon-modal__option--compact{justify-content:center}.lucide-icon-modal__option--compact span{display:none}.lucide-icon-modal__empty{grid-column:1 / -1;text-align:center;font-size:var(--font-ui-smaller);color:var(--text-muted);padding:var(--size-4-2)}.icon-selection-field{display:flex;flex-wrap:wrap;gap:var(--size-4-2)}.icon-selection-field__column{flex:1;min-width:220px}.icon-selection-field__label{display:block;font-size:var(--font-ui-smaller);font-weight:var(--font-medium);color:var(--text-muted);margin-bottom:var(--size-2-1)}.icon-selection-field__emoji-input{max-width:260px}.icon-selection-field__hint{margin-top:var(--size-2-1);font-size:var(--font-ui-smaller);color:var(--text-muted)}.status-icon{display:inline-flex;align-items:center;justify-content:center;line-height:1}.obsidian-icon{display:inline-flex;align-items:center;justify-content:center}.obsidian-icon svg{width:100%;height:100%}.note-status-editor-toolbar-badge{display:inline-flex;align-items:center;margin:0 var(--size-2-1)}.note-status-editor-toolbar-badge--hidden{display:none}.editor-toolbar-button__icon{font-size:16px;display:flex;align-items:center;justify-content:center;color:var(--text-muted);width:18px;height:18px;line-height:1}.editor-toolbar-button__icon--has-status{color:var(--interactive-accent)}.editor-toolbar-button__icon-container{position:relative;display:flex;align-items:center;justify-content:center}.editor-toolbar-button__counter{position:absolute;top:-4px;right:-6px;min-width:12px;height:12px;padding:0 2px;background:var(--interactive-accent);color:var(--text-on-accent);font-size:8px;font-weight:var(--font-semibold);border-radius:6px;display:flex;align-items:center;justify-content:center;line-height:1;border:1px solid var(--background-primary)} +.status-bar-enable-button{cursor:pointer;opacity:var(--icon-opacity);transition:opacity var(--anim-duration-fast) ease}.status-bar-enable-button:hover{opacity:var(--icon-opacity-hover)}.status-bar-group-row{display:flex;align-items:center;gap:var(--size-4-2)}.status-bar-group-container{display:flex;gap:var(--size-2-1);flex-wrap:wrap;align-items:center}@keyframes status-slide-in{0%{opacity:0;transform:scale(.8) translateY(4px)}to{opacity:1;transform:scale(1) translateY(0)}}.setting-item-full{width:100%}.setting-item-vertical{flex-direction:column;align-items:flex-start;gap:var(--size-2-2)}.custom-status-item{background:var(--background-secondary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);padding:var(--size-4-3);margin-bottom:var(--size-4-2);transition:all var(--anim-duration-fast) ease}.custom-status-item:hover{border-color:var(--interactive-hover)}.custom-status-item__row{display:flex;align-items:end;gap:var(--size-4-2);margin-bottom:var(--size-4-2)}.custom-status-item__row--icons{align-items:stretch;flex-wrap:wrap}.custom-status-item__field{display:flex;flex-direction:column;gap:var(--size-2-1)}.custom-status-item__field--icon,.custom-status-item__field--lucide{flex:1;min-width:240px}.custom-status-item__field--name,.custom-status-item__field--description{flex:2;min-width:120px}.custom-status-item__field--color{flex:0 0 auto}.custom-status-item__field--reorder,.custom-status-item__field--actions{flex:0 0 auto;padding-top:var(--size-4-1)}.custom-status-item__label{font-size:var(--font-ui-smaller);font-weight:var(--font-medium);color:var(--text-muted);margin-bottom:var(--size-2-1)}.custom-status-item__label--required{color:var(--text-error)}.custom-status-item__input--icon{width:100%!important;text-align:left;font-size:1em}.custom-status-item__hint{margin:0;font-size:var(--font-ui-smaller);color:var(--text-muted)}.custom-status-item__input--name{font-weight:var(--font-medium)}.custom-status-item__input--name.custom-status-item__input--invalid{border-color:var(--text-error)!important;background:var(--background-modifier-error)!important}.custom-status-item__input--description{color:var(--text-muted)}.custom-status-item__input--color{width:40px!important;height:32px!important;border-radius:var(--radius-s);cursor:pointer;border:2px solid var(--background-modifier-border)!important}.custom-status-item__input--color:hover{border-color:var(--interactive-hover)!important}.custom-status-item__remove-btn{width:32px;height:32px;border-radius:var(--radius-s);background:var(--background-modifier-error);color:var(--text-on-accent);border:none;cursor:pointer;font-size:18px;font-weight:700;transition:all var(--anim-duration-fast) ease}.custom-status-item__remove-btn:hover{background:var(--text-error);transform:scale(1.05)}.custom-status-item__reorder-buttons{display:flex;flex-direction:column;gap:2px}.custom-status-item__reorder-btn{width:24px;height:24px;border-radius:var(--radius-s);background:var(--background-modifier-border);color:var(--text-normal);border:none;cursor:pointer;font-size:12px;font-weight:700;transition:all var(--anim-duration-fast) ease;display:flex;align-items:center;justify-content:center;line-height:1}.custom-status-item__reorder-btn:hover:not(:disabled){background:var(--interactive-hover);transform:scale(1.05)}.custom-status-item__reorder-btn:disabled{opacity:.5;cursor:not-allowed}.custom-status-item__preview{display:flex;align-items:center;gap:var(--size-2-2);padding:var(--size-2-3) var(--size-4-2);background:var(--background-primary-alt);border-radius:var(--radius-s);border-left:3px solid var(--background-modifier-border);font-size:var(--font-ui-small)}.custom-status-item__preview-icon{font-size:1.1em;min-width:20px;text-align:center}.custom-status-item__preview-text{font-weight:var(--font-medium)}.custom-status-item__preview-desc{color:var(--text-muted);font-style:italic}.custom-status-item__preview-desc:before{content:"\2014 "}.custom-status-item__error{color:var(--text-error);font-size:var(--font-ui-smaller);margin-top:var(--size-2-1);padding:var(--size-2-1) var(--size-2-2);background:var(--background-modifier-error);border-radius:var(--radius-s);border-left:3px solid var(--text-error)}.custom-status-list__empty{text-align:center;padding:var(--size-4-4) var(--size-4-2);color:var(--text-muted);font-style:italic;border:2px dashed var(--background-modifier-border);border-radius:var(--radius-m);background:var(--background-primary-alt)}.custom-status-list__empty p{margin:0}.status-group{margin-bottom:var(--size-4-4);padding:var(--size-4-3);background:var(--background-primary-alt);border-radius:var(--radius-m);border:1px solid var(--background-modifier-border)}.status-group__header{margin-bottom:var(--size-4-2);padding-bottom:var(--size-2-2);border-bottom:1px solid var(--background-modifier-border)}.status-group__title{font-weight:var(--font-semibold);color:var(--text-normal);font-size:var(--font-ui-medium)}.status-group__description{font-size:var(--font-ui-smaller);color:var(--text-muted);margin-top:var(--size-2-1);line-height:var(--line-height-tight)}.status-group__items{display:flex;flex-direction:column;gap:var(--size-2-2)}.status-selector{display:flex;align-items:center;justify-content:space-between;padding:var(--size-2-3) var(--size-4-2);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background:var(--background-primary);cursor:pointer;transition:all var(--anim-duration-fast) ease;position:relative}.status-selector:hover{background:var(--background-modifier-hover);border-color:var(--background-modifier-border-hover)}.status-selector--selected{background:var(--background-modifier-success);border-color:var(--interactive-success)}.status-selector--selected:hover{background:var(--background-modifier-success-hover)}.status-selector__content{flex:1;min-width:0}.status-selector__status{display:flex;align-items:center;gap:var(--size-2-2);margin-bottom:var(--size-2-1)}.status-selector__icon{font-size:var(--font-ui-medium);width:var(--size-4-3);text-align:center;flex-shrink:0}.status-selector__name{font-weight:var(--font-medium);color:var(--text-normal)}.status-selector__description{font-size:var(--font-ui-smaller);color:var(--text-muted);line-height:var(--line-height-tight)}.status-selector__checkbox{margin-left:var(--size-4-2);pointer-events:none;flex-shrink:0}.quick-commands-container{margin-top:var(--size-4-2)}.note-status-options{max-height:300px;overflow-y:auto;border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--background-primary)}.status-mapping-list{display:flex;flex-direction:column;gap:var(--size-4-2);width:100%}.status-mapping-row{border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);padding:var(--size-4-3);background:var(--background-primary-alt);display:flex;flex-direction:column;gap:var(--size-2-2)}.status-mapping-empty{padding:var(--size-4-3);text-align:center;color:var(--text-muted);font-style:italic;border:1px dashed var(--background-modifier-border);border-radius:var(--radius-m)}.status-mapping-row__fields{display:flex;flex-wrap:wrap;gap:var(--size-4-2);width:100%}.status-mapping-field{display:flex;flex-direction:column;gap:var(--size-2-1);flex:1 1 180px}.status-mapping-field label{font-size:var(--font-ui-smaller);color:var(--text-muted)}.status-mapping-field small{color:var(--text-faint)}.status-mapping-field select,.status-mapping-field input{width:100%}.status-mapping-row__actions{display:flex;justify-content:flex-end}.status-mapping-remove-btn{background:var(--background-modifier-error);color:var(--text-on-accent);border:none;border-radius:var(--radius-s);padding:4px 10px;cursor:pointer}.status-mapping-remove-btn:hover{opacity:.9}.status-mapping-add-btn{align-self:flex-start;padding:var(--size-2-2) var(--size-4-2);border-radius:var(--radius-s);border:1px solid var(--background-modifier-border);background:var(--interactive-normal);cursor:pointer}.status-mapping-add-btn:hover{background:var(--interactive-hover)}.note-status-option{align-items:stretch}.note-status-option .selectable-list-item-icon{display:flex;align-items:center;justify-content:center}.note-status-option__icon-wrapper{display:inline-flex;align-items:center;justify-content:center;padding:var(--size-2-1);border-radius:var(--radius-m);border:1px solid var(--background-modifier-border);background:var(--background-secondary);min-width:36px;transition:border-color var(--anim-duration-fast) ease,background var(--anim-duration-fast) ease}.note-status-option:hover .note-status-option__icon-wrapper,.note-status-option.selectable-list-item[data-selected=true] .note-status-option__icon-wrapper{border-color:var(--interactive-accent);background:var(--background-modifier-hover)}.note-status-option__text{display:flex;flex-direction:column;gap:2px}.note-status-option__name{font-weight:var(--font-medium);color:var(--text-normal)}.note-status-option__description{font-size:var(--font-ui-smaller);color:var(--text-muted)}.note-status-chips{display:flex;flex-wrap:wrap;gap:var(--size-2-2);min-height:32px;align-items:center}.selectable-list-item{display:flex;align-items:center;gap:var(--size-4-2);padding:var(--size-2-3) var(--size-4-2);cursor:pointer;border-bottom:1px solid var(--background-modifier-border);transition:background var(--anim-duration-fast) ease}.selectable-list-item:hover{background:var(--background-modifier-hover)}.selectable-list-item[data-focused=true]{outline:2px solid var(--interactive-accent);outline-offset:-2px}.selectable-list-item[data-selected=true]{background:var(--background-modifier-hover)}.selectable-list-item-icon{font-size:16px;min-width:20px}.selectable-list-item-content{flex:1;font-size:var(--font-ui-small)}.selectable-list-item-check{color:var(--interactive-accent)}.template-settings-actions{display:flex;justify-content:end;gap:var(--size-4-2);padding-bottom:var(--size-4-2)}.template-create-btn{display:flex;align-items:center;gap:var(--size-2-2);padding:var(--size-2-3) var(--size-4-3);font-weight:var(--font-medium);font-size:var(--font-ui-medium)}.template-reset-btn{display:flex;align-items:center;gap:var(--size-2-2);padding:var(--size-2-3) var(--size-4-3);background:var(--interactive-normal);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);color:var(--text-normal);cursor:pointer;font-size:var(--font-ui-medium);transition:all var(--anim-duration-fast) ease}.template-reset-btn:hover{background:var(--interactive-hover);color:var(--text-normal)}.template-section{margin-bottom:var(--size-4-6)}.template-section-title{display:flex;align-items:center;gap:var(--size-2-2);font-size:var(--font-ui-large);font-weight:var(--font-semibold);color:var(--text-normal);margin-bottom:var(--size-4-3);padding-bottom:var(--size-2-2);border-bottom:2px solid var(--background-modifier-border)}.template-section-title .lucide{opacity:.8;color:var(--text-muted)}.template-list{display:flex;flex-direction:column;gap:var(--size-4-2)}.template-item{position:relative;display:flex;align-items:stretch;border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background:var(--background-primary-alt);transition:all var(--anim-duration-fast) ease;overflow:hidden}.template-item:hover{background:var(--background-modifier-hover);border-color:var(--background-modifier-border-hover);box-shadow:0 2px 8px var(--shadow-color)}.template-item.enabled{background:var(--background-modifier-hover);border-color:var(--interactive-accent);box-shadow:0 0 0 1px var(--interactive-accent)}.template-item.enabled:hover{box-shadow:0 2px 8px var(--shadow-color),0 0 0 1px var(--interactive-accent)}.template-item-content{flex:1;border:none!important;border-radius:0!important;background:transparent!important}.template-item-main{flex:1}.template-header{display:flex;align-items:center;gap:var(--size-2-2);margin-bottom:var(--size-2-1)}.template-custom-badge{display:flex;align-items:center;gap:var(--size-2-1);padding:var(--size-2-1) var(--size-2-2);background:var(--interactive-accent);color:var(--text-on-accent);font-size:var(--font-ui-smaller);font-weight:var(--font-medium);border-radius:var(--radius-s);text-transform:uppercase;letter-spacing:.05em}.template-item-actions{display:flex;flex-direction:column;padding:var(--size-4-2);gap:var(--size-2-1);background:var(--background-modifier-border);border-left:1px solid var(--background-modifier-border-hover);min-width:44px;align-items:center;justify-content:center}.template-editor-modal{background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-l);box-shadow:var(--shadow-l);max-width:800px;width:100%}.template-editor-modal__header{padding:var(--size-4-4) var(--size-4-6);background:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border);border-radius:var(--radius-l) var(--radius-l) 0 0}.template-editor-modal__header h2{margin:0;font-size:var(--font-ui-larger);font-weight:var(--font-bold);color:var(--text-normal)}.template-editor-modal__content{padding:var(--size-4-6);max-height:70vh;overflow-y:auto}.template-editor-statuses{display:flex;flex-direction:column;gap:var(--size-4-3)}.template-editor-add-status{display:flex;align-items:center;gap:var(--size-2-2);align-self:flex-start;margin-top:var(--size-4-2)}.template-editor-add-status .lucide{width:16px;height:16px}.template-editor-modal__actions{display:flex;gap:var(--size-4-2);justify-content:flex-end;padding:var(--size-4-4) var(--size-4-6);background:var(--background-secondary);border-top:1px solid var(--background-modifier-border);border-radius:0 0 var(--radius-l) var(--radius-l)}.template-editor-modal__actions button{padding:var(--size-2-3) var(--size-4-4);border-radius:var(--radius-m);font-size:var(--font-ui-medium);font-weight:var(--font-medium);cursor:pointer;transition:all var(--anim-duration-fast) ease;border:1px solid var(--background-modifier-border)}.template-editor-modal__actions button:not(.mod-cta){background:var(--interactive-normal);color:var(--text-normal)}.template-editor-modal__actions button:not(.mod-cta):hover{background:var(--interactive-hover)}.template-editor-modal__actions .mod-cta:disabled{opacity:.5;cursor:not-allowed;transform:none}.template-editor-modal__actions .mod-cta:disabled:hover{transform:none}@media (max-width: 768px){.template-settings-actions,.template-item{flex-direction:column}.template-item-actions{flex-direction:row;border-left:none;border-top:1px solid var(--background-modifier-border-hover);min-width:unset;padding:var(--size-2-3) var(--size-4-2)}.template-editor-modal{margin:var(--size-4-2);max-width:calc(100% - var(--size-4-4))}}.template-editor-modal__content::-webkit-scrollbar{width:var(--scrollbar-width)}.template-editor-modal__content::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb-bg);border-radius:var(--radius-s)}.template-editor-modal__content::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-active-bg)}.status-dashboard-view-container{height:100%;overflow:hidden}.status-dashboard{background:var(--background-primary)}.status-dashboard-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-3) var(--size-4-4);background:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border)}.status-dashboard-title{margin:0;font-size:var(--font-ui-larger);font-weight:var(--font-bold)}.status-dashboard-refresh{padding:var(--size-2-2) var(--size-2-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--interactive-normal);color:var(--text-normal);cursor:pointer;font-size:var(--font-ui-medium);transition:all var(--anim-duration-fast) ease}.status-dashboard-refresh:hover{background:var(--interactive-hover);transform:rotate(90deg)}.status-dashboard-content{flex:1;overflow-y:auto;padding:var(--size-4-3);display:flex;flex-direction:column;gap:var(--size-4-4)}.status-dashboard-section{background:var(--background-secondary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);overflow:hidden}.status-dashboard-section-header{padding:var(--size-4-2) var(--size-4-3);background:var(--background-modifier-hover);border-bottom:1px solid var(--background-modifier-border)}.status-dashboard-section-header h3{margin:0;font-size:var(--font-ui-large);font-weight:var(--font-semibold)}.status-dashboard-current-note{padding:var(--size-4-3)}.current-note-info{margin-bottom:var(--size-4-3);padding-bottom:var(--size-4-2);border-bottom:1px solid var(--background-modifier-border)}.current-note-name{font-size:var(--font-ui-large);font-weight:var(--font-semibold);margin-bottom:var(--size-2-1)}.current-note-path{font-size:var(--font-ui-small);color:var(--text-muted);font-family:var(--font-monospace);margin-bottom:var(--size-2-1)}.current-note-modified{font-size:var(--font-ui-smaller);color:var(--text-faint);font-style:italic}.current-note-statuses{display:flex;flex-direction:column;gap:var(--size-4-2)}.current-note-tag-group{display:flex;flex-direction:column;gap:var(--size-2-2)}.current-note-status-list{display:flex;flex-wrap:wrap;gap:var(--size-2-2)}.current-note-no-status{font-size:var(--font-ui-small);color:var(--text-muted);font-style:italic;padding:var(--size-2-1) var(--size-2-3);background:var(--background-modifier-border);border-radius:var(--radius-s)}.current-note-empty{text-align:center;color:var(--text-muted);font-style:italic;padding:var(--size-4-4)}.vault-overview{padding:var(--size-4-3)}.vault-stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:var(--size-4-2)}.vault-stat-card{text-align:center;padding:var(--size-4-2);background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);transition:all var(--anim-duration-fast) ease}.vault-stat-card:hover{background:var(--background-modifier-hover);transform:translateY(-2px);box-shadow:var(--shadow-s)}.vault-stat-number{font-size:var(--font-ui-largest);font-weight:var(--font-bold);color:var(--interactive-accent);margin-bottom:var(--size-2-1)}.vault-stat-label{font-size:var(--font-ui-small);color:var(--text-muted);font-weight:var(--font-medium);text-transform:uppercase;letter-spacing:.05em}.status-distribution{padding:var(--size-4-3)}.status-chart{display:flex;flex-direction:column;gap:var(--size-4-2)}.status-chart-item{display:flex;flex-direction:column;gap:var(--size-2-1)}.status-chart-item--clickable{cursor:pointer;transition:all var(--anim-duration-fast) ease;padding:var(--size-2-2);border-radius:var(--radius-s)}.status-chart-item--clickable:hover{background:var(--background-modifier-hover);transform:translateY(-1px)}.status-chart-info{display:flex;align-items:center;gap:var(--size-2-3)}.status-chart-count{font-size:var(--font-ui-small);color:var(--text-muted);font-weight:var(--font-medium)}.status-chart-bar{height:8px;background:var(--background-modifier-border);border-radius:var(--radius-s);overflow:hidden}.status-chart-fill{height:100%;transition:width var(--anim-duration-moderate) ease}.status-distribution-empty{text-align:center;color:var(--text-muted);font-style:italic;padding:var(--size-4-4)}.quick-actions{padding:var(--size-4-3);display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:var(--size-4-3)}.quick-actions-group{display:flex;flex-direction:column;gap:var(--size-2-2)}.quick-actions-group-title{font-size:var(--font-ui-small);font-weight:var(--font-semibold);color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;padding-bottom:var(--size-2-1);border-bottom:1px solid var(--background-modifier-border);margin-bottom:var(--size-2-1)}.quick-action-btn{width:100%;padding:var(--size-2-3) var(--size-4-2);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);background:var(--interactive-normal);color:var(--text-normal);cursor:pointer;font-size:var(--font-ui-medium);font-weight:var(--font-medium);text-align:left;display:flex;align-items:center;gap:var(--size-2-2);transition:all var(--anim-duration-fast) ease}.quick-action-btn:hover:not(:disabled){background:var(--interactive-hover);transform:translateY(-1px);box-shadow:var(--shadow-s)}.quick-action-btn:disabled{opacity:.5;cursor:not-allowed;background:var(--background-modifier-border);color:var(--text-faint)}@media (max-width: 768px){.quick-actions{grid-template-columns:1fr}}.status-dashboard-loading{display:flex;align-items:center;justify-content:center;height:100%;font-size:var(--font-ui-medium);color:var(--text-muted)}.status-dashboard-disabled{display:flex;align-items:center;justify-content:center;height:100%;padding:var(--size-4-4);text-align:center;font-size:var(--font-ui-medium);color:var(--text-muted);background:var(--background-secondary);border:2px dashed var(--background-modifier-border);border-radius:var(--radius-m);margin:var(--size-4-4)}.status-dashboard-content::-webkit-scrollbar{width:var(--scrollbar-width)}.status-dashboard-content::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb-bg);border-radius:var(--radius-s)}.status-dashboard-content::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-active-bg)}.grouped-status-view-container{height:100%;overflow:hidden}.grouped-status-header{padding:var(--size-4-2) var(--size-4-3);background:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border)}.grouped-status-title{margin:0 0 var(--size-4-2) 0;font-size:var(--font-ui-large);font-weight:var(--font-semibold)}.grouped-status-filters{display:flex;flex-direction:column;gap:var(--size-4-2)}.grouped-status-note-input{width:100%;padding:var(--size-2-2) var(--size-2-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--background-primary);color:var(--text-normal);font-size:var(--font-ui-medium)}.grouped-status-note-input:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--background-modifier-accent)}.grouped-status-content{flex:1;overflow-y:auto;padding:var(--size-4-2)}.grouped-status-tag-section{margin-bottom:var(--size-4-3)}.grouped-status-tag-header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-3);background:var(--background-secondary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);cursor:pointer;transition:all var(--anim-duration-fast) ease;margin-bottom:var(--size-4-2)}.grouped-status-tag-header:hover{background:var(--background-modifier-hover)}.grouped-status-tag-content{padding-left:var(--size-4-2)}.grouped-status-group{margin-bottom:var(--size-4-2);background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);overflow:hidden;box-shadow:0 1px 3px #0000001a;transition:box-shadow var(--anim-duration-fast) ease}.grouped-status-group:hover{box-shadow:0 2px 8px #00000026}.grouped-status-group__header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4-2) var(--size-4-3);cursor:pointer;transition:background var(--anim-duration-fast) ease;position:relative}.grouped-status-group__header:hover{background:var(--background-modifier-hover)}.grouped-status-group__header:focus{outline:2px solid var(--color-accent);outline-offset:-2px}.grouped-status-group__status{display:flex;align-items:center;gap:var(--size-2-2);min-width:0;flex:1}.grouped-status-group__template-badge{display:inline-flex;align-items:center;padding:var(--size-2-1) var(--size-2-2);background:var(--background-modifier-border);border-radius:var(--radius-s);font-size:var(--font-ui-smaller);color:var(--text-muted);font-weight:var(--font-medium)}.grouped-status-group__info{display:flex;align-items:center;gap:var(--size-2-2);margin-left:auto}.grouped-status-files{background:var(--background-secondary);border-top:1px solid var(--background-modifier-border)}.grouped-status-files-list{max-height:300px;overflow-y:auto}.grouped-status-file-item{padding:var(--size-2-3) var(--size-4-3);cursor:pointer;transition:background var(--anim-duration-fast) ease;border-bottom:1px solid var(--background-modifier-border-hover)}.grouped-status-file-item:hover{background:var(--background-modifier-hover)}.grouped-status-file-item:last-child{border-bottom:none}.grouped-status-file-name{font-weight:var(--font-medium);color:var(--text-normal);margin-bottom:2px}.grouped-status-file-path{font-size:var(--font-ui-small);color:var(--text-muted);opacity:.8}.grouped-status-load-more{display:flex;justify-content:center;padding:var(--size-4-2);border-top:1px solid var(--background-modifier-border)}.grouped-status-load-btn{padding:var(--size-2-2) var(--size-4-2);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--interactive-normal);color:var(--text-normal);cursor:pointer;font-size:var(--font-ui-small);font-weight:var(--font-medium);transition:all var(--anim-duration-fast) ease}.grouped-status-load-btn:hover{background:var(--interactive-hover);transform:translateY(-1px)}.grouped-status-loading,.grouped-status-empty{display:flex;align-items:center;justify-content:center;padding:var(--size-4-6);color:var(--text-muted);font-size:var(--font-ui-medium)}.grouped-status-view-disabled{display:flex;align-items:center;justify-content:center;height:100%;padding:var(--size-4-4);text-align:center;font-size:var(--font-ui-medium);color:var(--text-muted);background:var(--background-secondary);border:2px dashed var(--background-modifier-border);border-radius:var(--radius-m);margin:var(--size-4-4)}.grouped-status-content::-webkit-scrollbar,.grouped-status-files-list::-webkit-scrollbar{width:var(--scrollbar-width)}.grouped-status-content::-webkit-scrollbar-thumb,.grouped-status-files-list::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb-bg);border-radius:var(--radius-s)}.grouped-status-content::-webkit-scrollbar-thumb:hover,.grouped-status-files-list::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-active-bg)}.grouped-status-filters__template{display:flex;flex-direction:column;gap:var(--size-2-1)}.grouped-status-filters__template-select{width:100%;padding:var(--size-2-2) var(--size-2-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--background-primary);color:var(--text-normal);font-size:var(--font-ui-small);cursor:pointer}.grouped-status-filters__template-select:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--background-modifier-accent)}.grouped-status-filters__note{display:flex;flex-direction:column;gap:var(--size-2-1)}.grouped-status-filters__note-input{width:100%;padding:var(--size-2-2) var(--size-2-3);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s);background:var(--background-primary);color:var(--text-normal);font-size:var(--font-ui-small)}.grouped-status-filters__note-input:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 2px var(--background-modifier-accent)}.grouped-status-group__status{display:flex;align-items:center;gap:var(--size-2-2)}.grouped-status-group__template-badge{background:var(--background-modifier-border);color:var(--text-muted);padding:var(--size-2-1) var(--size-2-2);border-radius:var(--radius-s);font-size:var(--font-ui-smaller);font-weight:var(--font-medium);text-transform:capitalize;white-space:nowrap}.custom-icon{display:inline-flex}.custom-icon__absolute-right{position:absolute;right:0}.note-status-colored-block{position:relative;border-radius:var(--radius-s);z-index:0}.note-status-colored-block:before{content:"";position:absolute;inset:0;border-radius:inherit;background-color:var(--note-status-block-color, transparent);opacity:.18;pointer-events:none;z-index:-1;transition:background-color var(--anim-duration-fast) ease,opacity var(--anim-duration-fast) ease}.note-status-border-left{position:relative}.note-status-border-left:after{content:"";position:absolute;left:0;top:2px;bottom:2px;width:3px;border-radius:var(--radius-s);background-color:var(--note-status-border-color, transparent);opacity:.35;pointer-events:none}.note-status-underline{text-decoration-line:underline;text-decoration-thickness:2px;text-decoration-color:var(--note-status-underline-color, currentColor);text-underline-offset:3px}.note-status-dot-badge{width:6px;height:6px;border-radius:50%;margin-left:var(--size-2-1);display:inline-flex;align-items:center;justify-content:center;box-shadow:0 0 0 1px var(--background-modifier-border);flex-shrink:0}.status-wrapper{position:relative;display:inline-block}.status-minimal{display:inline-flex;align-items:center;gap:2px;margin-left:var(--size-2-2);padding:2px;border-radius:var(--radius-s);cursor:pointer;opacity:.7;transition:all var(--anim-duration-fast) ease}.status-minimal:hover{opacity:1;background:var(--background-modifier-hover);transform:scale(1.05)}.status-minimal--compact{margin-left:0;padding:0;background:transparent;gap:var(--size-2-1)}.status-minimal--compact .status-minimal__icon{font-size:var(--font-ui-medium)}.status-minimal[data-status-color] .status-minimal__count{background:var(--interactive-accent)}.status-minimal__icon{font-size:11px;line-height:1;display:inline-flex;align-items:center;justify-content:center;filter:grayscale(.2);transition:filter var(--anim-duration-fast) ease}.status-minimal:hover .status-minimal__icon{filter:grayscale(0)}.status-minimal__count{min-width:14px;height:14px;padding:0 3px;background:var(--interactive-accent);color:var(--text-on-accent);font-size:var(--font-ui-smaller);font-weight:var(--font-semibold);border-radius:var(--radius-m);display:flex;align-items:center;justify-content:center}.status-info-popup{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:var(--layer-popover);background:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);box-shadow:var(--shadow-l);min-width:280px;max-width:400px;overflow:hidden;pointer-events:none}.status-popup-header{display:flex;align-items:center;gap:var(--size-2-2);padding:var(--size-4-2) var(--size-4-3);background:var(--background-secondary);border-bottom:1px solid var(--background-modifier-border);font-weight:var(--font-semibold);font-size:var(--font-ui-small)}.status-popup-content{padding:var(--size-4-2)}.status-popup-empty{padding:var(--size-4-4);text-align:center;color:var(--text-muted);font-style:italic}.status-group{margin-bottom:var(--size-4-2)}.status-group:last-child{margin-bottom:0}.status-group__header{display:flex;justify-content:space-between;align-items:center;padding:var(--size-2-2) var(--size-2-3);background:var(--background-secondary-alt);border-radius:var(--radius-s);margin-bottom:var(--size-2-2);border:1px solid var(--background-modifier-border);transition:background var(--anim-duration-fast) ease}.status-group__header:hover{background:var(--background-modifier-hover)}.status-group__name{text-transform:capitalize;font-weight:var(--font-medium);font-size:var(--font-ui-smaller);color:var(--text-normal)}.status-group__count{display:inline-flex;align-items:center;padding:var(--size-2-1) var(--size-2-2);background:var(--background-modifier-border);border-radius:var(--radius-s);font-size:var(--font-ui-smaller);color:var(--text-muted);font-weight:var(--font-medium);min-width:20px;justify-content:center}.status-group__items{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:var(--size-2-2);padding:0 var(--size-2-1)}.status-item{display:flex;flex-direction:column;gap:var(--size-2-1);padding:var(--size-2-2);background:var(--background-primary-alt);border:1px solid var(--background-modifier-border);border-radius:var(--radius-s)}.status-description{font-size:var(--font-ui-smaller);color:var(--text-muted);line-height:1.4}.group-label-container{display:flex;align-items:center;gap:var(--size-2-2);font-size:var(--font-ui-small);font-weight:var(--font-semibold);text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);transition:color var(--anim-duration-fast) ease}.group-label-container--highlighted{color:var(--text-normal)}.group-label-container svg{width:16px;height:16px;opacity:.8}.collapsible-counter-container{padding:var(--size-2-1) var(--size-2-3);border-radius:var(--radius-s);background:var(--background-modifier-border);border:1px solid var(--background-modifier-border-hover);color:var(--text-muted);font-weight:var(--font-medium);font-size:var(--font-ui-smaller);cursor:pointer;transition:all var(--anim-duration-fast) ease;min-width:28px;text-align:center}.collapsible-counter-container:hover{background:var(--background-modifier-border-hover);color:var(--text-normal)}.note-status-chip{display:inline-flex;align-items:center;gap:var(--size-2-2);padding:var(--size-2-1) var(--size-2-3);background:var(--interactive-accent);color:var(--text-on-accent);border-radius:var(--radius-s);font-size:var(--font-ui-smaller);cursor:pointer;transition:all var(--anim-duration-fast) ease}.note-status-chip:hover{opacity:.9;transform:translateY(-1px)}.note-status-chip--removing{opacity:.5;transform:scale(.95)}.note-status-chip__icon{font-size:1em}.note-status-chip__text{font-weight:var(--font-medium)}.note-status-chip__remove{display:flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;background:rgba(255,255,255,.2);cursor:pointer;margin-left:var(--size-2-1)}.note-status-chip__remove:hover{background:rgba(255,255,255,.3)}.status-badge-container{display:inline-flex;align-items:center;padding:var(--size-2-1) var(--size-2-3);border-radius:var(--radius-s);transition:all var(--anim-duration-fast) ease;cursor:pointer}.status-badge-item{display:flex;align-items:center;gap:var(--size-2-1)}.status-badge-icon{font-size:var(--font-ui-small)}.status-badge-text{font-weight:var(--font-medium);font-size:var(--font-ui-small)}.template-status-chip{display:inline-flex;align-items:center;gap:var(--size-2-1);padding:var(--size-2-1) var(--size-2-3);background:var(--background-secondary);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);font-size:var(--font-ui-smaller)}.template-status-color-dot{width:8px;height:8px;border-radius:50%;background-color:var(--dot-color)}.lucide-icon-picker{position:relative;display:flex;flex-direction:column;gap:var(--size-2-1)}.lucide-icon-picker__text-input{max-width:240px}.lucide-icon-picker__row{display:flex;gap:var(--size-2-1);align-items:center}.lucide-icon-picker__modal-button{display:inline-flex;align-items:center;gap:var(--size-2-1);border:1px solid var(--background-modifier-border);border-radius:var(--radius-m);padding:var(--size-2-1) var(--size-4-1);background:var(--background-secondary);cursor:pointer;transition:border var(--anim-duration-fast) ease,background var(--anim-duration-fast) ease;min-width:220px;justify-content:space-between}.lucide-icon-picker__modal-button:hover{border-color:var(--interactive-accent)}.lucide-icon-picker__preview{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px}.lucide-icon-picker__label{flex:1;text-align:left;font-size:var(--font-ui-smaller);color:var(--text-muted)}.lucide-icon-picker__clear-trigger{background:transparent;border:none;color:var(--text-muted);font-size:var(--font-ui-smaller);cursor:pointer;padding:var(--size-2-1) var(--size-2-2)}.lucide-icon-picker__clear-trigger:hover{color:var(--text-normal);text-decoration:underline}.lucide-icon-modal-wrapper{padding:var(--size-4-2)}.lucide-icon-modal{display:flex;flex-direction:column;gap:var(--size-2-1)}.lucide-icon-modal__header{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--size-2-2)}.lucide-icon-modal__count{margin:0;font-size:var(--font-ui-smaller);color:var(--text-muted)}.lucide-icon-modal__close{display:inline-flex;align-items:center;justify-content:center}.lucide-icon-modal__search{width:100%}.lucide-icon-modal__toggle{display:inline-flex;align-items:center;gap:var(--size-2-1);font-size:var(--font-ui-smaller);color:var(--text-muted)}.lucide-icon-modal__list{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:var(--size-2-1);max-height:360px;overflow-y:auto;padding-right:var(--size-2-1)}.lucide-icon-modal__list--icons-only{grid-template-columns:repeat(auto-fill,minmax(70px,1fr))}.lucide-icon-modal__option{display:flex;align-items:center;gap:var(--size-2-1);padding:var(--size-2-1);border-radius:var(--radius-s);border:1px solid var(--background-modifier-border);background:var(--background-secondary);cursor:pointer;font-size:var(--font-ui-smaller);justify-content:flex-start}.lucide-icon-modal__option:hover{background:var(--background-modifier-hover)}.lucide-icon-modal__option--selected{border-color:var(--interactive-accent);background:var(--background-modifier-success)}.lucide-icon-modal__option--compact{justify-content:center}.lucide-icon-modal__option--compact span{display:none}.lucide-icon-modal__empty{grid-column:1 / -1;text-align:center;font-size:var(--font-ui-smaller);color:var(--text-muted);padding:var(--size-4-2)}.icon-selection-field{display:flex;flex-wrap:wrap;gap:var(--size-4-2)}.icon-selection-field__column{flex:1;min-width:220px}.icon-selection-field__label{display:block;font-size:var(--font-ui-smaller);font-weight:var(--font-medium);color:var(--text-muted);margin-bottom:var(--size-2-1)}.icon-selection-field__emoji-input{max-width:260px}.icon-selection-field__hint{margin-top:var(--size-2-1);font-size:var(--font-ui-smaller);color:var(--text-muted)}.status-icon{display:inline-flex;align-items:center;justify-content:center;line-height:1}.obsidian-icon{display:inline-flex;align-items:center;justify-content:center}.obsidian-icon svg{width:100%;height:100%}.note-status-editor-toolbar-badge{display:inline-flex;align-items:center;margin:0 var(--size-2-1)}.note-status-editor-toolbar-badge--hidden{display:none}.editor-toolbar-button__icon{font-size:16px;display:flex;align-items:center;justify-content:center;color:var(--text-muted);width:18px;height:18px;line-height:1}.editor-toolbar-button__icon--has-status{color:var(--interactive-accent)}.editor-toolbar-button__icon-container{position:relative;display:flex;align-items:center;justify-content:center}.editor-toolbar-button__counter{position:absolute;top:-4px;right:-6px;min-width:12px;height:12px;padding:0 2px;background:var(--interactive-accent);color:var(--text-on-accent);font-size:8px;font-weight:var(--font-semibold);border-radius:6px;display:flex;align-items:center;justify-content:center;line-height:1;border:1px solid var(--background-primary)} diff --git a/styles/components/settings.css b/styles/components/settings.css index b69770f..cba5e3f 100644 --- a/styles/components/settings.css +++ b/styles/components/settings.css @@ -350,6 +350,78 @@ border-radius: var(--radius-s); background: var(--background-primary); } +.status-mapping-list { + display: flex; + flex-direction: column; + gap: var(--size-4-2); + width: 100%; +} +.status-mapping-row { + border: 1px solid var(--background-modifier-border); + border-radius: var(--radius-m); + padding: var(--size-4-3); + background: var(--background-primary-alt); + display: flex; + flex-direction: column; + gap: var(--size-2-2); +} +.status-mapping-empty { + padding: var(--size-4-3); + text-align: center; + color: var(--text-muted); + font-style: italic; + border: 1px dashed var(--background-modifier-border); + border-radius: var(--radius-m); +} +.status-mapping-row__fields { + display: flex; + flex-wrap: wrap; + gap: var(--size-4-2); + width: 100%; +} +.status-mapping-field { + display: flex; + flex-direction: column; + gap: var(--size-2-1); + flex: 1 1 180px; +} +.status-mapping-field label { + font-size: var(--font-ui-smaller); + color: var(--text-muted); +} +.status-mapping-field small { + color: var(--text-faint); +} +.status-mapping-field select, +.status-mapping-field input { + width: 100%; +} +.status-mapping-row__actions { + display: flex; + justify-content: flex-end; +} +.status-mapping-remove-btn { + background: var(--background-modifier-error); + color: var(--text-on-accent); + border: none; + border-radius: var(--radius-s); + padding: 4px 10px; + cursor: pointer; +} +.status-mapping-remove-btn:hover { + opacity: 0.9; +} +.status-mapping-add-btn { + align-self: flex-start; + padding: var(--size-2-2) var(--size-4-2); + border-radius: var(--radius-s); + border: 1px solid var(--background-modifier-border); + background: var(--interactive-normal); + cursor: pointer; +} +.status-mapping-add-btn:hover { + background: var(--interactive-hover); +} .note-status-option { /* Uses SelectableListItem styles */ diff --git a/types/pluginSettings.ts b/types/pluginSettings.ts index 90af6f0..896999a 100644 --- a/types/pluginSettings.ts +++ b/types/pluginSettings.ts @@ -1,5 +1,20 @@ import { NoteStatus } from "./noteStatus"; +export type StatusFrontmatterMapping = + | { + id: string; + scope: "template"; + templateId: string; + frontmatterKeys: string[]; + } + | { + id: string; + scope: "status"; + templateId?: string; + statusName: string; + frontmatterKeys: string[]; + }; + export interface StatusTemplate { id: string; name: string; @@ -51,5 +66,6 @@ export type PluginSettings = { editorToolbarButtonPosition: "left" | "right" | "right-before"; // Position of the toolbar button editorToolbarButtonDisplay: "all-notes" | "active-only"; // Whether to show button in all notes or only active one applyStatusRecursivelyToSubfolders: boolean; // Whether to show recursive folder context option + statusFrontmatterMappings: StatusFrontmatterMapping[]; // Custom mappings between templates/statuses and frontmatter keys [key: string]: unknown; }; diff --git a/utils/frontmatterMappings.ts b/utils/frontmatterMappings.ts new file mode 100644 index 0000000..fa1e7b1 --- /dev/null +++ b/utils/frontmatterMappings.ts @@ -0,0 +1,111 @@ +import { + PluginSettings, + StatusFrontmatterMapping, +} from "@/types/pluginSettings"; +import { + NoteStatus, + ScopedStatusName, + StatusIdentifier, +} from "@/types/noteStatus"; + +type StatusLike = NoteStatus | ScopedStatusName | StatusIdentifier; + +const normalizeKeys = (keys: string[]): string[] => { + const unique = new Set( + keys + .map((key) => key?.trim()) + .filter((key): key is string => Boolean(key)), + ); + return Array.from(unique); +}; + +const toScopedStatusName = (status: StatusLike): ScopedStatusName => { + if (typeof status === "string") { + if (status.includes(":")) { + const [templateId, name] = status.split(":", 2); + return { templateId, name }; + } + return { name: status }; + } + + if ("name" in status) { + return { + name: status.name, + templateId: "templateId" in status ? status.templateId : undefined, + }; + } + + return status; +}; + +const matchesStatusMapping = ( + mapping: StatusFrontmatterMapping, + status: ScopedStatusName, +) => { + if (mapping.scope === "status") { + const templateMatches = + (mapping.templateId || null) === (status.templateId || null); + return templateMatches && mapping.statusName === status.name; + } + + if (mapping.scope === "template") { + return Boolean(status.templateId) && + status.templateId === mapping.templateId + ? true + : false; + } + + return false; +}; + +const getMappingKeys = (mapping: StatusFrontmatterMapping): string[] => { + return normalizeKeys(mapping.frontmatterKeys || []); +}; + +export const getKnownFrontmatterKeys = (settings: PluginSettings): string[] => { + const keys = [ + settings.tagPrefix, + ...(settings.statusFrontmatterMappings ?? []).flatMap((mapping) => + getMappingKeys(mapping), + ), + ]; + return normalizeKeys(keys); +}; + +export const resolveFrontmatterKeysForStatus = ( + status: StatusLike, + settings: PluginSettings, + options?: { isMarkdownFile?: boolean }, +): string[] => { + if (options?.isMarkdownFile === false) { + return [settings.tagPrefix]; + } + + const scoped = toScopedStatusName(status); + const mappings = settings.statusFrontmatterMappings ?? []; + + const statusMapping = mappings.find( + (mapping) => + mapping.scope === "status" && matchesStatusMapping(mapping, scoped), + ); + if (statusMapping) { + const keys = getMappingKeys(statusMapping); + if (keys.length) { + return keys; + } + } + + const templateMapping = mappings.find( + (mapping) => + mapping.scope === "template" && + matchesStatusMapping(mapping, scoped), + ); + if (templateMapping) { + const keys = getMappingKeys(templateMapping); + if (keys.length) { + return keys; + } + } + + return [settings.tagPrefix]; +};