feat: add settings to customize the status bar when unknown status; custom unknown icon

This commit is contained in:
Aleix Soler 2025-07-20 20:02:43 +02:00
parent 75ebd05d15
commit feaf3fc448
7 changed files with 165 additions and 7 deletions

View file

@ -6,10 +6,20 @@ type Props = {
onMouseEnter: (statuses: GroupedStatuses) => void;
onMouseLeave: (statuses: GroupedStatuses) => void;
hideUnknownStatus?: boolean;
unknownStatusConfig?: {
icon: string;
color: string;
};
};
export const FileExplorerIcon: FC<Props> = memo(
({ statuses, onMouseLeave, onMouseEnter, hideUnknownStatus }) => {
({
statuses,
onMouseLeave,
onMouseEnter,
hideUnknownStatus,
unknownStatusConfig,
}) => {
const statusEntries = Object.entries(statuses);
const totalStatuses = statusEntries.reduce(
(acc, [, list]) => acc + list.length,
@ -20,7 +30,10 @@ export const FileExplorerIcon: FC<Props> = memo(
// If hideUnknownStatus is enabled, don't show anything for files without status
if (hideUnknownStatus) return null;
// Show "no status" icon
// Use config passed from integration, with fallbacks
const icon = unknownStatusConfig?.icon || "❓";
const color = unknownStatusConfig?.color || "#8b949e";
return (
<div className="status-wrapper">
<div
@ -29,11 +42,11 @@ export const FileExplorerIcon: FC<Props> = memo(
onMouseLeave={() => onMouseLeave({})}
style={
{
"--primary-color": "var(--text-muted)",
"--primary-color": color,
} as React.CSSProperties
}
>
<span className="status-icon"></span>
<span className="status-icon">{icon}</span>
</div>
</div>
);

View file

@ -15,6 +15,18 @@ export const UISettings: React.FC<Props> = ({ settings, onChange }) => {
onChange(key, e.target.checked);
};
const handleInputChange =
(key: keyof PluginSettings) =>
(e: React.ChangeEvent<HTMLInputElement>) => {
onChange(key, e.target.value);
};
const handleTextInputChange =
(key: keyof PluginSettings) =>
(e: React.ChangeEvent<HTMLInputElement>) => {
onChange(key, e.target.value);
};
return (
<div className="ui-settings">
<h3>User interface</h3>
@ -90,6 +102,67 @@ export const UISettings: React.FC<Props> = ({ settings, onChange }) => {
onChange={handleChange("excludeUnknownStatus")}
/>
</SettingItem>
<h4>Unknown Status Customization</h4>
<SettingItem
name="Unknown status icon"
description="Custom icon to display for files with unknown status"
>
<input
type="text"
value={settings.unknownStatusIcon || "❓"}
onChange={handleInputChange("unknownStatusIcon")}
style={{ width: "60px", textAlign: "center" }}
/>
</SettingItem>
<SettingItem
name="Unknown status color"
description="Custom hex color for unknown status (e.g., #8b949e)"
>
<input
type="color"
value={settings.unknownStatusColor || "#8b949e"}
onChange={handleInputChange("unknownStatusColor")}
style={{ width: "50px", height: "30px" }}
/>
</SettingItem>
<SettingItem
name="Status bar 'no status' text"
description="Custom text to display in status bar when there is no status"
>
<input
type="text"
value={settings.statusBarNoStatusText}
onChange={handleTextInputChange("statusBarNoStatusText")}
style={{ width: "150px" }}
placeholder="No status"
/>
</SettingItem>
<SettingItem
name="Show icon in status bar for 'no status'"
description="Display the unknown status icon alongside the text in the status bar"
>
<input
type="checkbox"
checked={settings.statusBarShowNoStatusIcon || false}
onChange={handleChange("statusBarShowNoStatusIcon")}
/>
</SettingItem>
<SettingItem
name="Show text in status bar for 'no status'"
description="Display the custom text in the status bar when there is no status"
>
<input
type="checkbox"
checked={settings.statusBarShowNoStatusText ?? true}
onChange={handleChange("statusBarShowNoStatusText")}
/>
</SettingItem>
</div>
);
};

View file

@ -11,25 +11,53 @@ export type Props = {
templates?: { [key: string]: { description: string } };
hideIfNotStatuses?: boolean;
onStatusClick: StatusBarContextProps["onStatusClick"];
noStatusConfig?: {
text: string;
showIcon: boolean;
showText: boolean;
icon: string;
color: string;
};
};
export const StatusBar: FC<Props> = ({
statuses,
hideIfNotStatuses,
onStatusClick,
noStatusConfig,
}) => {
const statusEntries = Object.entries(statuses);
const hasStatuses = statusEntries.flatMap((s) => s[1]).length > 0;
if (!hasStatuses) {
if (hideIfNotStatuses) return null;
if (
!noStatusConfig ||
(!noStatusConfig.showIcon && !noStatusConfig.showText)
) {
return null;
}
return (
<span
className="status-bar-item mod-clickable"
onClick={() => onStatusClick({ name: "", icon: "" })}
style={{ cursor: "pointer" }}
style={{
cursor: "pointer",
color: noStatusConfig.color,
}}
>
No status
{noStatusConfig.showIcon && (
<span
style={{
marginRight: noStatusConfig.showText ? "4px" : "0",
}}
>
{noStatusConfig.icon}
</span>
)}
{noStatusConfig.showText && noStatusConfig.text}
</span>
);
}

View file

@ -22,4 +22,10 @@ export const DEFAULT_PLUGIN_SETTINGS: PluginSettings = {
strictStatuses: false, // Default to show all statuses from frontmatter
excludeUnknownStatus: true, // Default to exclude unknown status files for better performance
quickStatusCommands: ["active", "completed"], // Add default quick commands
// Unknown status customization
unknownStatusIcon: "❓",
unknownStatusColor: "#8b949e",
statusBarNoStatusText: "No status",
statusBarShowNoStatusIcon: false,
statusBarShowNoStatusText: true,
};

View file

@ -55,7 +55,9 @@ export class FileExplorerIntegration implements IElementProcessor {
key === "useMultipleStatuses" ||
key === "tagPrefix" ||
key === "strictStatuses" ||
key === "fileExplorerIconPosition"
key === "fileExplorerIconPosition" ||
key === "unknownStatusIcon" ||
key === "unknownStatusColor"
) {
this.destroy();
this.integrate().catch((r) => console.error(r));
@ -108,6 +110,14 @@ export class FileExplorerIntegration implements IElementProcessor {
}
}
private getUnknownStatusConfig() {
const settings = settingsService.settings;
return {
icon: settings.unknownStatusIcon || "❓",
color: settings.unknownStatusColor || "#8b949e",
};
}
render(element: Element, statuses: GroupedStatuses): void {
// Remove existing icon
const existingIcon = element.querySelector(`.${this.ICON_CLASS}`);
@ -136,6 +146,7 @@ export class FileExplorerIntegration implements IElementProcessor {
hideUnknownStatus={
settingsService.settings.hideUnknownStatusInExplorer
}
unknownStatusConfig={this.getUnknownStatusConfig()}
/>,
);

View file

@ -61,6 +61,15 @@ export class StatusBarIntegration {
if (key === "customStatuses") {
this.handleActiveFileChange().catch(console.error); // INFO: Force a re-read of the statuses and render
}
if (
key === "unknownStatusIcon" ||
key === "unknownStatusColor" ||
key === "statusBarNoStatusText" ||
key === "statusBarShowNoStatusIcon" ||
key === "statusBarShowNoStatusText"
) {
this.render(); // INFO: Force a render for unknown status customization
}
},
"statusBarIntegrationSubscription2",
);
@ -108,6 +117,17 @@ export class StatusBarIntegration {
});
}
private getNoStatusConfig() {
const settings = settingsService.settings;
return {
text: settings.statusBarNoStatusText || "No status",
showIcon: settings.statusBarShowNoStatusIcon || false,
showText: settings.statusBarShowNoStatusText ?? true,
icon: settings.unknownStatusIcon || "❓",
color: settings.unknownStatusColor || "#8b949e",
};
}
private render() {
if (!this.root) {
this.root = createRoot(this.statusBarContainer);
@ -129,6 +149,7 @@ export class StatusBarIntegration {
settingsService.settings.autoHideStatusBar
}
onStatusClick={() => this.openStatusModal()}
noStatusConfig={this.getNoStatusConfig()}
/>,
);
}

View file

@ -25,5 +25,11 @@ export type PluginSettings = {
strictStatuses: boolean; // Whether to only show known statuses
excludeUnknownStatus: boolean; // Whether to exclude files with unknown status from the status pane
quickStatusCommands: string[];
// Unknown status customization
unknownStatusIcon: string; // Custom icon for unknown status
unknownStatusColor: string; // Custom hex color for unknown status
statusBarNoStatusText: string; // Custom text for status bar when no status
statusBarShowNoStatusIcon: boolean; // Whether to show icon in status bar for no status
statusBarShowNoStatusText: boolean; // Whether to show text in status bar for no status
[key: string]: unknown;
};