diff --git a/components/StatusDashboard/QuickActionsPanel.tsx b/components/StatusDashboard/QuickActionsPanel.tsx
index 3353834..6b7f873 100644
--- a/components/StatusDashboard/QuickActionsPanel.tsx
+++ b/components/StatusDashboard/QuickActionsPanel.tsx
@@ -1,58 +1,167 @@
-import { BaseNoteStatusService } from "@/core/noteStatusService";
-import settingsService from "@/core/settingsService";
+export type DashboardAction =
+ | "refresh"
+ | "open-grouped-view"
+ | "find-unassigned"
+ | "change-status"
+ | "insert-metadata"
+ | "cycle-status"
+ | "clear-status"
+ | "copy-status"
+ | "paste-status"
+ | "search-by-status"
+ | "toggle-multiple-mode"
+ | "set-quick-status";
interface QuickActionsPanelProps {
- onRefresh: () => void;
+ hasCurrentFile: boolean;
+ useMultipleStatuses: boolean;
+ quickStatusCommands: Array<{ name: string; command: string }>;
+ availableStatuses: Array<{ name: string }>;
+ onAction: (action: DashboardAction, value?: string) => void;
}
-export const QuickActionsPanel = ({ onRefresh }: QuickActionsPanelProps) => {
+export const QuickActionsPanel = ({
+ hasCurrentFile,
+ useMultipleStatuses,
+ quickStatusCommands,
+ availableStatuses,
+ onAction,
+}: QuickActionsPanelProps) => {
return (
Quick Actions
-
{
- const leaf =
- BaseNoteStatusService.app.workspace.getLeaf();
- leaf.setViewState({
- type: "grouped-status-view",
- active: true,
- });
- }}
- >
- 📊 View Grouped Statuses
-
-
{
- const files =
- BaseNoteStatusService.app.vault.getMarkdownFiles();
- const filesWithoutStatus = files.filter((file) => {
- const cachedMetadata =
- BaseNoteStatusService.app.metadataCache.getFileCache(
- file,
- );
- const frontmatter = cachedMetadata?.frontmatter;
- return (
- !frontmatter ||
- !frontmatter[settingsService.settings.tagPrefix]
- );
- });
+ {/* Views Group */}
+
+
📊 Views
+
onAction("open-grouped-view")}
+ title="Open grouped status view"
+ >
+ 📊 Grouped Statuses
+
+
onAction("find-unassigned")}
+ title="Find notes without status"
+ >
+ 🔍 Find Unassigned Notes
+
+
- console.log(
- `Found ${filesWithoutStatus.length} notes without status:`,
- filesWithoutStatus.map((f) => f.path),
- );
- }}
- >
- 🔍 Find Notes Without Status
-
-
- 🔄 Refresh Data
-
+ {/* Quick Status Group - Only show when not in multi-status mode and has quick statuses */}
+ {!useMultipleStatuses && quickStatusCommands.length > 0 && (
+
+
+ ⚡ Quick Status
+
+ {quickStatusCommands.map((status) => (
+
+ onAction("set-quick-status", status.name)
+ }
+ disabled={!hasCurrentFile}
+ title={`Set status to ${status.name}`}
+ >
+ ⚡ {status.name}
+
+ ))}
+
+ )}
+
+ {/* Current Note Group */}
+
+
+ 📝 Current Note
+
+
onAction("change-status")}
+ disabled={!hasCurrentFile}
+ title="Change status of current note"
+ >
+ 📝 Change Status
+
+
onAction("insert-metadata")}
+ disabled={!hasCurrentFile}
+ title="Insert status metadata in editor"
+ >
+ 📝 Insert Metadata
+
+
onAction("cycle-status")}
+ disabled={!hasCurrentFile || useMultipleStatuses}
+ title="Cycle through available statuses"
+ >
+ 📝 Cycle Status
+
+
onAction("clear-status")}
+ disabled={!hasCurrentFile}
+ title="Clear status from current note"
+ >
+ 📝 Clear Status
+
+
+
+ {/* Clipboard Group */}
+
+
+ 📋 Clipboard
+
+
onAction("copy-status")}
+ disabled={!hasCurrentFile}
+ title="Copy status to clipboard"
+ >
+ 📋 Copy Status
+
+
onAction("paste-status")}
+ disabled={!hasCurrentFile}
+ title="Paste status from clipboard"
+ >
+ 📋 Paste Status
+
+
+
+ {/* Tools Group */}
+
+
⚙️ Tools
+
onAction("search-by-status")}
+ disabled={!hasCurrentFile}
+ title="Search for notes with same status"
+ >
+ ⚙️ Search by Status
+
+
onAction("toggle-multiple-mode")}
+ title="Toggle multiple statuses mode"
+ >
+ ⚙️ Toggle Multi-Status
+
+
onAction("refresh")}
+ title="Refresh dashboard data"
+ >
+ ⚙️ Refresh Data
+
+
);
diff --git a/components/StatusDashboard/StatusDashboard.tsx b/components/StatusDashboard/StatusDashboard.tsx
index b72ffb4..442e473 100644
--- a/components/StatusDashboard/StatusDashboard.tsx
+++ b/components/StatusDashboard/StatusDashboard.tsx
@@ -4,11 +4,20 @@ import eventBus from "@/core/eventBus";
import { VaultStatsCard } from "./VaultStatsCard";
import { CurrentNoteSection } from "./CurrentNoteSection";
import { StatusDistributionChart } from "./StatusDistributionChart";
-import { QuickActionsPanel } from "./QuickActionsPanel";
+import { QuickActionsPanel, DashboardAction } from "./QuickActionsPanel";
import { useVaultStats } from "./useVaultStats";
import { useCurrentNote } from "./useCurrentNote";
+import { PluginSettings } from "@/types/pluginSettings";
-export const StatusDashboard = () => {
+interface StatusDashboardProps {
+ onAction: (action: DashboardAction, value?: string) => void;
+ settings: PluginSettings;
+}
+
+export const StatusDashboard = ({
+ onAction,
+ settings,
+}: StatusDashboardProps) => {
const [isLoading, setIsLoading] = useState(true);
const { vaultStats, updateVaultStats } = useVaultStats();
const { currentNote, updateCurrentNote } = useCurrentNote();
@@ -23,6 +32,17 @@ export const StatusDashboard = () => {
}
}, [updateVaultStats, updateCurrentNote]);
+ const handleAction = useCallback(
+ (action: DashboardAction, value?: string) => {
+ if (action === "refresh") {
+ loadData();
+ } else {
+ onAction(action, value);
+ }
+ },
+ [loadData, onAction],
+ );
+
useEffect(() => {
loadData();
@@ -90,7 +110,18 @@ export const StatusDashboard = () => {
-
+ ({
+ name,
+ command: `note-status:set-status-${name}`,
+ }),
+ )}
+ availableStatuses={BaseNoteStatusService.getAllAvailableStatuses()}
+ onAction={handleAction}
+ />
);
diff --git a/components/atoms/Input.tsx b/components/atoms/Input.tsx
index 832691e..184af6e 100644
--- a/components/atoms/Input.tsx
+++ b/components/atoms/Input.tsx
@@ -9,6 +9,8 @@ interface BaseInputProps {
placeholder?: string;
className?: string;
style?: React.CSSProperties;
+ onFocus?: () => void;
+ onBlur?: () => void;
}
interface TextInputProps extends BaseInputProps {
@@ -57,7 +59,19 @@ const getInputStyles = (variant: InputVariant): React.CSSProperties => {
};
export const Input = React.forwardRef(
- ({ variant, value, onChange, placeholder, className, style }, ref) => {
+ (
+ {
+ variant,
+ value,
+ onChange,
+ placeholder,
+ className,
+ style,
+ onFocus,
+ onBlur,
+ },
+ ref,
+ ) => {
const inputStyles = getInputStyles(variant);
const combinedStyles = { ...inputStyles, ...style };
@@ -70,6 +84,8 @@ export const Input = React.forwardRef(
placeholder={placeholder}
className={className}
style={combinedStyles}
+ onFocus={onFocus}
+ onBlur={onBlur}
/>
);
},
diff --git a/integrations/views/status-dashboard-view.tsx b/integrations/views/status-dashboard-view.tsx
index 381d9e2..acd880a 100644
--- a/integrations/views/status-dashboard-view.tsx
+++ b/integrations/views/status-dashboard-view.tsx
@@ -1,6 +1,15 @@
-import { ItemView, WorkspaceLeaf } from "obsidian";
+import { ItemView, WorkspaceLeaf, Notice, App } from "obsidian";
import { Root, createRoot } from "react-dom/client";
import { StatusDashboard } from "@/components/StatusDashboard/StatusDashboard";
+import { DashboardAction } from "@/components/StatusDashboard/QuickActionsPanel";
+import { BaseNoteStatusService } from "@/core/noteStatusService";
+import settingsService from "@/core/settingsService";
+
+interface AppWithCommands extends App {
+ commands: {
+ executeCommandById(commandId: string): boolean;
+ };
+}
export const VIEW_TYPE_STATUS_DASHBOARD = "status-dashboard-view";
@@ -23,13 +32,105 @@ export class StatusDashboardView extends ItemView {
return "bar-chart-2";
}
+ private handleAction = (action: DashboardAction, value?: string) => {
+ const appWithCommands = this.app as AppWithCommands;
+
+ switch (action) {
+ case "open-grouped-view":
+ this.openGroupedView();
+ break;
+ case "find-unassigned":
+ this.findUnassignedNotes();
+ break;
+ case "change-status":
+ appWithCommands.commands.executeCommandById(
+ "note-status:change-status",
+ );
+ break;
+ case "insert-metadata":
+ appWithCommands.commands.executeCommandById(
+ "note-status:insert-status-metadata",
+ );
+ break;
+ case "cycle-status":
+ appWithCommands.commands.executeCommandById(
+ "note-status:cycle-status",
+ );
+ break;
+ case "clear-status":
+ appWithCommands.commands.executeCommandById(
+ "note-status:clear-status",
+ );
+ break;
+ case "copy-status":
+ appWithCommands.commands.executeCommandById(
+ "note-status:copy-status",
+ );
+ break;
+ case "paste-status":
+ appWithCommands.commands.executeCommandById(
+ "note-status:paste-status",
+ );
+ break;
+ case "search-by-status":
+ appWithCommands.commands.executeCommandById(
+ "note-status:search-by-status",
+ );
+ break;
+ case "toggle-multiple-mode":
+ appWithCommands.commands.executeCommandById(
+ "note-status:toggle-multiple-statuses",
+ );
+ break;
+ case "set-quick-status":
+ if (value) {
+ const commandId = `note-status:set-status-${value}`;
+ const result =
+ appWithCommands.commands.executeCommandById(commandId);
+ if (!result) {
+ new Notice(
+ `Quick status command failed: ${value}. Make sure the status exists and multiple statuses mode is disabled.`,
+ );
+ }
+ }
+ break;
+ }
+ };
+
+ private openGroupedView() {
+ const leaf = BaseNoteStatusService.app.workspace.getLeaf();
+ leaf.setViewState({ type: "grouped-status-view", active: true });
+ }
+
+ private findUnassignedNotes() {
+ const files = BaseNoteStatusService.app.vault.getMarkdownFiles();
+ const filesWithoutStatus = files.filter((file) => {
+ const cachedMetadata =
+ BaseNoteStatusService.app.metadataCache.getFileCache(file);
+ const frontmatter = cachedMetadata?.frontmatter;
+ return (
+ !frontmatter || !frontmatter[settingsService.settings.tagPrefix]
+ );
+ });
+ new Notice(`Found ${filesWithoutStatus.length} notes without status`);
+ console.log(
+ "Notes without status:",
+ filesWithoutStatus.map((f) => f.path),
+ );
+ }
+
async onOpen() {
const container = this.containerEl.children[1];
container.empty();
container.addClass("status-dashboard-view-container");
this.root = createRoot(container);
- this.root.render( );
+ this.root.render(
+ ,
+ );
}
async onClose() {
diff --git a/styles/components/dashboard.css b/styles/components/dashboard.css
index 968002a..8135634 100644
--- a/styles/components/dashboard.css
+++ b/styles/components/dashboard.css
@@ -229,14 +229,30 @@
/* Quick Actions */
.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-wrap: wrap;
- gap: var(--size-4-2);
+ 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: 0.05em;
+ padding-bottom: var(--size-2-1);
+ border-bottom: 1px solid var(--background-modifier-border);
+ margin-bottom: var(--size-2-1);
}
.quick-action-btn {
- flex: 1;
- min-width: 200px;
+ width: 100%;
padding: var(--size-2-3) var(--size-4-2);
border: 1px solid var(--background-modifier-border);
border-radius: var(--radius-m);
@@ -245,16 +261,33 @@
cursor: pointer;
font-size: var(--font-ui-medium);
font-weight: var(--font-medium);
- text-align: center;
+ text-align: left;
+ display: flex;
+ align-items: center;
+ gap: var(--size-2-2);
transition: all var(--anim-duration-fast) ease;
}
-.quick-action-btn:hover {
+.quick-action-btn:hover:not(:disabled) {
background: var(--interactive-hover);
transform: translateY(-1px);
box-shadow: var(--shadow-s);
}
+.quick-action-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ background: var(--background-modifier-border);
+ color: var(--text-faint);
+}
+
+/* Mobile responsive */
+@media (max-width: 768px) {
+ .quick-actions {
+ grid-template-columns: 1fr;
+ }
+}
+
/* States */
.status-dashboard-loading {
display: flex;