diff --git a/components/ChangeStatusModal/CurrentStatusChips.tsx b/components/ChangeStatusModal/CurrentStatusChips.tsx index 6155e0b..444e0e1 100644 --- a/components/ChangeStatusModal/CurrentStatusChips.tsx +++ b/components/ChangeStatusModal/CurrentStatusChips.tsx @@ -26,7 +26,7 @@ export const CurrentStatusChips: React.FC = ({ > {currentStatuses.map((status) => ( searchFilter - ? availableStatuses.filter((status) => - status.name - .toLowerCase() - .includes(searchFilter.toLowerCase()), - ) + ? availableStatuses.filter((status) => { + const searchTerm = searchFilter.toLowerCase(); + return ( + status.name.toLowerCase().includes(searchTerm) || + (status.templateId && + status.templateId + .toLowerCase() + .includes(searchTerm)) + ); + }) : availableStatuses, [searchFilter, availableStatuses], ); diff --git a/components/SettingsUI.tsx/index.tsx b/components/SettingsUI.tsx/index.tsx index 1a573f9..b59b7ee 100644 --- a/components/SettingsUI.tsx/index.tsx +++ b/components/SettingsUI.tsx/index.tsx @@ -1,11 +1,11 @@ import { useState, useEffect } from "react"; import { PluginSettings } from "@/types/pluginSettings"; import { UISettings } from "./UISettings"; -import { PREDEFINED_TEMPLATES } from "@/constants/defaultSettings"; import { TemplateSettings } from "./TemplateSettings"; import { BehaviourSettings } from "./BehaviourSettings"; import { CustomStatusSettings } from "./CustomStatusSettings"; import { QuickCommandsSettings } from "./QuickCommandsSettings"; +import { PREDEFINED_TEMPLATES } from "@/constants/predefinedTemplates"; export type Props = { settings: PluginSettings; diff --git a/components/atoms/StatusDisplay.tsx b/components/atoms/StatusDisplay.tsx index e1d603f..34c5d1f 100644 --- a/components/atoms/StatusDisplay.tsx +++ b/components/atoms/StatusDisplay.tsx @@ -16,6 +16,12 @@ export const StatusDisplay: FC = memo( ({ status, variant, removable = false, onRemove, onClick }) => { const [isRemoving, setIsRemoving] = useState(false); + const getDisplayName = () => { + return status.templateId + ? `${status.name} (${status.templateId})` + : status.name; + }; + const handleRemove = (e: React.MouseEvent) => { e.stopPropagation(); if (onRemove) { @@ -55,7 +61,9 @@ export const StatusDisplay: FC = memo( {status.icon ? status.icon : "๐Ÿ“"} - {status.name} + + {getDisplayName()} + {removable && (
= memo( {status.icon ? status.icon : "๐Ÿ“"} - {status.name} + + {getDisplayName()} +
); @@ -122,7 +132,7 @@ export const StatusDisplay: FC = memo( } /> - {status.icon ? status.icon : "๐Ÿ“"} {status.name} + {status.icon ? status.icon : "๐Ÿ“"} {getDisplayName()} ); diff --git a/components/atoms/StatusSelector.tsx b/components/atoms/StatusSelector.tsx index d1b2dcc..b256056 100644 --- a/components/atoms/StatusSelector.tsx +++ b/components/atoms/StatusSelector.tsx @@ -12,6 +12,10 @@ interface StatusOptionProps { export const StatusModalOption: React.FC = memo( ({ status, isSelected, isFocused, onSelect }) => { + const displayName = status.templateId + ? `${status.name} (${status.templateId})` + : status.name; + return ( = memo( status.description ? getStatusTooltip(status) : undefined } > - {status.name} + {displayName} ); }, @@ -63,7 +67,7 @@ export const StatusSelector: React.FC = ({ > {availableStatuses.map((status, index) => ( s.name, - ); + BaseNoteStatusService.getAllAvailableStatuses(); if (allStatuses.length === 0) { new Notice("No statuses available"); @@ -80,7 +78,9 @@ export class CommandsService { : undefined; let nextIndex = 0; if (currentStatus) { - const currentIndex = allStatuses.indexOf(currentStatus); + const currentIndex = allStatuses.findIndex( + (s) => s.name === currentStatus, + ); if (currentIndex !== -1) { nextIndex = currentIndex === -1 @@ -89,15 +89,21 @@ export class CommandsService { } } + const nextStatus = allStatuses[nextIndex]; + const scopedIdentifier = nextStatus.templateId + ? BaseNoteStatusService.formatStatusIdentifier({ + templateId: nextStatus.templateId, + name: nextStatus.name, + }) + : nextStatus.name; + statusService .addStatus( settingsService.settings.tagPrefix, - allStatuses[nextIndex], + scopedIdentifier, ) .then((resolve) => { - new Notice( - `Status changed to ${allStatuses[nextIndex]}`, - ); + new Notice(`Status changed to ${nextStatus.name}`); }); } return true; @@ -250,10 +256,17 @@ export class CommandsService { !settingsService.settings.useMultipleStatuses ) { const statusService = this.createStatusService(file); + const scopedIdentifier = status.templateId + ? BaseNoteStatusService.formatStatusIdentifier({ + templateId: status.templateId, + name: status.name, + }) + : status.name; + statusService .overrideStatuses( settingsService.settings.tagPrefix, - [statusName], + [scopedIdentifier], ) .then(() => { new Notice(`Status set to ${statusName}`); diff --git a/core/noteStatusService.ts b/core/noteStatusService.ts index e797587..ad545f2 100644 --- a/core/noteStatusService.ts +++ b/core/noteStatusService.ts @@ -3,10 +3,12 @@ import { GroupedStatuses, NoteStatus, NoteStatus as NoteStatusType, + StatusIdentifier, + ScopedStatusName, } from "@/types/noteStatus"; -import { PREDEFINED_TEMPLATES } from "@/constants/defaultSettings"; import settingsService from "@/core/settingsService"; import eventBus from "./eventBus"; +import { PREDEFINED_TEMPLATES } from "@/constants/predefinedTemplates"; export abstract class BaseNoteStatusService { static app: App; @@ -20,6 +22,44 @@ export abstract class BaseNoteStatusService { BaseNoteStatusService.app = app; } + static parseStatusIdentifier( + identifier: StatusIdentifier, + ): ScopedStatusName { + if (typeof identifier === "string") { + if (identifier.includes(":")) { + const [templateId, name] = identifier.split(":", 2); + return { templateId, name }; + } + return { name: identifier }; + } + return identifier; + } + + static formatStatusIdentifier(scopedName: ScopedStatusName): string { + if (scopedName.templateId) { + return `${scopedName.templateId}:${scopedName.name}`; + } + return scopedName.name; + } + + static resolveStatusFromIdentifier( + identifier: StatusIdentifier, + ): NoteStatus | undefined { + const parsed = BaseNoteStatusService.parseStatusIdentifier(identifier); + const availableStatuses = + BaseNoteStatusService.getAllAvailableStatuses(); + + if (parsed.templateId) { + return availableStatuses.find( + (s) => + s.name === parsed.name && + s.templateId === parsed.templateId, + ); + } + + return availableStatuses.find((s) => s.name === parsed.name); + } + private static allEnabledTemplatesStatuses(): NoteStatusType[] { if (settingsService.settings.useCustomStatusesOnly) { return []; @@ -53,11 +93,13 @@ export abstract class BaseNoteStatusService { // ); // } - protected statusNameToObject(statusName: NoteStatusType["name"]) { - const availableStatuses = - BaseNoteStatusService.getAllAvailableStatuses(); - const s = availableStatuses.find((f) => f.name === statusName); - return s; + protected statusNameToObject(statusName: string | StatusIdentifier) { + if (typeof statusName === "string") { + return BaseNoteStatusService.resolveStatusFromIdentifier( + statusName, + ); + } + return BaseNoteStatusService.resolveStatusFromIdentifier(statusName); } protected getStatusMetadataKeys(): string[] { @@ -71,7 +113,7 @@ export abstract class BaseNoteStatusService { ): Promise; abstract addStatus( frontmatterTagName: string, - statusIdentifier: NoteStatus["name"], + statusIdentifier: StatusIdentifier, ): Promise; } @@ -129,6 +171,13 @@ export class NoteStatusService extends BaseNoteStatusService { status: NoteStatus, ): Promise { let removed = false; + const targetIdentifier = status.templateId + ? BaseNoteStatusService.formatStatusIdentifier({ + templateId: status.templateId, + name: status.name, + }) + : status.name; + await BaseNoteStatusService.app.fileManager.processFrontMatter( this.file, (frontmatter) => { @@ -138,7 +187,7 @@ export class NoteStatusService extends BaseNoteStatusService { if (Array.isArray(noteStatusFrontmatter)) { const i = noteStatusFrontmatter.findIndex( - (statusName: string) => statusName === status.name, + (statusName: string) => statusName === targetIdentifier, ); if (i !== -1) { noteStatusFrontmatter.splice(i, 1); @@ -171,8 +220,13 @@ export class NoteStatusService extends BaseNoteStatusService { async overrideStatuses( frontmatterTagName: string, - statusIdentifiers: NoteStatus["name"][], + statusIdentifiers: StatusIdentifier[], ): Promise { + const formattedIdentifiers = statusIdentifiers.map((id) => + typeof id === "string" + ? id + : BaseNoteStatusService.formatStatusIdentifier(id), + ); await BaseNoteStatusService.app.fileManager.processFrontMatter( this.file, (frontmatter) => { @@ -181,9 +235,11 @@ export class NoteStatusService extends BaseNoteStatusService { Array.isArray(frontmatter[frontmatterTagName]) ) { frontmatter[frontmatterTagName].splice(0); - frontmatter[frontmatterTagName].push(...statusIdentifiers); + frontmatter[frontmatterTagName].push( + ...formattedIdentifiers, + ); } - frontmatter[frontmatterTagName] = [...statusIdentifiers]; + frontmatter[frontmatterTagName] = [...formattedIdentifiers]; }, ); eventBus.publish("frontmatter-manually-changed", { file: this.file }); @@ -192,16 +248,23 @@ export class NoteStatusService extends BaseNoteStatusService { async addStatus( frontmatterTagName: string, - statusIdentifier: NoteStatus["name"], + statusIdentifier: StatusIdentifier, ): Promise { let added = false; + const formattedIdentifier = + typeof statusIdentifier === "string" + ? statusIdentifier + : BaseNoteStatusService.formatStatusIdentifier( + statusIdentifier, + ); + await BaseNoteStatusService.app.fileManager.processFrontMatter( this.file, (frontmatter) => { const noteStatusFrontmatter = (frontmatter?.[frontmatterTagName] as string[]) || []; if (!settingsService.settings.useMultipleStatuses) { - frontmatter[frontmatterTagName] = [statusIdentifier]; + frontmatter[frontmatterTagName] = [formattedIdentifier]; added = true; } else { // Ensure frontmatter property exists as an array @@ -213,10 +276,13 @@ export class NoteStatusService extends BaseNoteStatusService { } const i = noteStatusFrontmatter.findIndex( - (statusName: string) => statusName === statusIdentifier, + (statusName: string) => + statusName === formattedIdentifier, ); if (i === -1) { - frontmatter[frontmatterTagName].push(statusIdentifier); + frontmatter[frontmatterTagName].push( + formattedIdentifier, + ); added = true; } } @@ -290,6 +356,12 @@ export class MultipleNoteStatusService extends BaseNoteStatusService { status: NoteStatus, ): Promise { let removedFromAny = false; + const targetIdentifier = status.templateId + ? BaseNoteStatusService.formatStatusIdentifier({ + templateId: status.templateId, + name: status.name, + }) + : status.name; const promises = this.files.map(async (file) => { let removed = false; @@ -302,13 +374,14 @@ export class MultipleNoteStatusService extends BaseNoteStatusService { if (Array.isArray(noteStatusFrontmatter)) { const index = noteStatusFrontmatter.findIndex( - (statusName: string) => statusName === status.name, + (statusName: string) => + statusName === targetIdentifier, ); if (index !== -1) { noteStatusFrontmatter.splice(index, 1); removed = true; } - } else if (noteStatusFrontmatter === status.name) { + } else if (noteStatusFrontmatter === targetIdentifier) { delete frontmatter[frontmatterTagName]; removed = true; } @@ -331,9 +404,15 @@ export class MultipleNoteStatusService extends BaseNoteStatusService { async addStatus( frontmatterTagName: string, - statusIdentifier: NoteStatus["name"], + statusIdentifier: StatusIdentifier, ): Promise { let addedToAny = false; + const formattedIdentifier = + typeof statusIdentifier === "string" + ? statusIdentifier + : BaseNoteStatusService.formatStatusIdentifier( + statusIdentifier, + ); const promises = this.files.map(async (file) => { let added = false; @@ -344,18 +423,20 @@ export class MultipleNoteStatusService extends BaseNoteStatusService { frontmatter[frontmatterTagName]; if (!noteStatusFrontmatter) { - frontmatter[frontmatterTagName] = [statusIdentifier]; + frontmatter[frontmatterTagName] = [formattedIdentifier]; added = true; } else if (Array.isArray(noteStatusFrontmatter)) { - if (!noteStatusFrontmatter.includes(statusIdentifier)) { - noteStatusFrontmatter.push(statusIdentifier); + if ( + !noteStatusFrontmatter.includes(formattedIdentifier) + ) { + noteStatusFrontmatter.push(formattedIdentifier); added = true; } } else { - if (noteStatusFrontmatter !== statusIdentifier) { + if (noteStatusFrontmatter !== formattedIdentifier) { frontmatter[frontmatterTagName] = [ noteStatusFrontmatter, - statusIdentifier, + formattedIdentifier, ]; added = true; } @@ -381,6 +462,13 @@ export class MultipleNoteStatusService extends BaseNoteStatusService { frontmatterTagName: string, status: NoteStatus, ): TFile[] { + const targetIdentifier = status.templateId + ? BaseNoteStatusService.formatStatusIdentifier({ + templateId: status.templateId, + name: status.name, + }) + : status.name; + return this.files.filter((file) => { const cachedMetadata = BaseNoteStatusService.app.metadataCache.getFileCache(file); @@ -391,9 +479,9 @@ export class MultipleNoteStatusService extends BaseNoteStatusService { if (!value) return false; if (Array.isArray(value)) { - return value.includes(status.name); + return value.includes(targetIdentifier); } - return value === status.name; + return value === targetIdentifier; }); } } diff --git a/integrations/modals/statusModalIntegration.tsx b/integrations/modals/statusModalIntegration.tsx index 2a7eefe..0a8b838 100644 --- a/integrations/modals/statusModalIntegration.tsx +++ b/integrations/modals/statusModalIntegration.tsx @@ -8,6 +8,7 @@ import SelectorService from "@/core/selectorService"; import { MultipleNoteStatusService, NoteStatusService, + BaseNoteStatusService, } from "@/core/noteStatusService"; import eventBus from "@/core/eventBus"; @@ -69,9 +70,16 @@ export class StatusModalIntegration extends Modal { frontmatterTagName, status, ) => { + const scopedIdentifier = status.templateId + ? BaseNoteStatusService.formatStatusIdentifier({ + templateId: status.templateId, + name: status.name, + }) + : status.name; + const added = await this.selectorService.noteStatusService.addStatus( frontmatterTagName, - status.name, + scopedIdentifier, ); if (added) { new Notice(`Status ${status.name} added successfully to the note`); diff --git a/types/noteStatus.ts b/types/noteStatus.ts index ef40123..17aff56 100644 --- a/types/noteStatus.ts +++ b/types/noteStatus.ts @@ -3,7 +3,15 @@ export type NoteStatus = { icon: string; color?: string; // Optional color property description?: string; // Optional description property + templateId?: string; // Optional template scope for namespacing [key: string]: unknown; }; +export type ScopedStatusName = { + templateId?: string; + name: string; +}; + +export type StatusIdentifier = string | ScopedStatusName; + export type GroupedStatuses = Record; diff --git a/utils/statusUtils.ts b/utils/statusUtils.ts index 4408c78..f43189a 100644 --- a/utils/statusUtils.ts +++ b/utils/statusUtils.ts @@ -8,7 +8,9 @@ export const isStatusSelected = ( status: NoteStatus, currentStatuses: NoteStatus[], ): boolean => { - return currentStatuses.some((s) => s.name === status.name); + return currentStatuses.some( + (s) => s.name === status.name && s.templateId === status.templateId, + ); }; /** @@ -49,13 +51,16 @@ export const getStatusDisplayName = ( }; /** - * Compares two statuses for equality by name + * Compares two statuses for equality by name and templateId */ export const isStatusEqual = ( status1: NoteStatus, status2: NoteStatus, ): boolean => { - return status1.name === status2.name; + return ( + status1.name === status2.name && + status1.templateId === status2.templateId + ); }; /**