feat: multiple visual styles

This commit is contained in:
Aleix Soler 2025-11-21 15:31:01 +01:00
parent 48945b3d63
commit 7bb982ce2c
6 changed files with 215 additions and 51 deletions

View file

@ -247,6 +247,41 @@ export const UISettings: React.FC<Props> = ({ settings, onChange }) => {
/>
</SettingItem>
<h4>Additional status visualization in file explorer</h4>
<SettingItem
name="Show colored left border"
description="Render a subtle colored strip on the left edge of the file explorer item."
>
<input
type="checkbox"
checked={settings.fileExplorerLeftBorder || false}
onChange={handleChange("fileExplorerLeftBorder")}
/>
</SettingItem>
<SettingItem
name="Show colored status dot"
description="Append a compact colored dot next to the filename as an extra status indicator."
>
<input
type="checkbox"
checked={settings.fileExplorerStatusDot || false}
onChange={handleChange("fileExplorerStatusDot")}
/>
</SettingItem>
<SettingItem
name="Underline filename with status color"
description="Adds a colored underline below the filename without changing the text color."
>
<input
type="checkbox"
checked={settings.fileExplorerUnderlineFileName || false}
onChange={handleChange("fileExplorerUnderlineFileName")}
/>
</SettingItem>
<SettingItem
name="Status icon frame"
description="Choose whether to display a frame around the status icon inside the file explorer."

View file

@ -24,6 +24,9 @@ export const DEFAULT_PLUGIN_SETTINGS: PluginSettings = {
hideUnknownStatusInExplorer: true, // Default to hide unknown status
fileExplorerColorFileName: false,
fileExplorerColorBlock: false,
fileExplorerLeftBorder: false,
fileExplorerStatusDot: false,
fileExplorerUnderlineFileName: false,
enabledTemplates: DEFAULT_ENABLED_TEMPLATES,
useCustomStatusesOnly: false,
useMultipleStatuses: true,

View file

@ -29,6 +29,11 @@ export class FileExplorerIntegration implements IElementProcessor {
private readonly FILE_NAME_ORIGINAL_COLOR_ATTR = "noteStatusOriginalColor";
private readonly FILE_BLOCK_CLASS = "note-status-colored-block";
private readonly FILE_BLOCK_COLOR_VAR = "--note-status-block-color";
private readonly FILE_BORDER_CLASS = "note-status-border-left";
private readonly FILE_BORDER_COLOR_VAR = "--note-status-border-color";
private readonly FILE_UNDERLINE_CLASS = "note-status-underline";
private readonly FILE_UNDERLINE_COLOR_VAR = "--note-status-underline-color";
private readonly STATUS_DOT_CLASS = "note-status-dot-badge";
constructor(plugin: Plugin) {
this.plugin = plugin;
@ -69,6 +74,9 @@ export class FileExplorerIntegration implements IElementProcessor {
key === "fileExplorerIconColorMode" ||
key === "fileExplorerColorFileName" ||
key === "fileExplorerColorBlock" ||
key === "fileExplorerLeftBorder" ||
key === "fileExplorerStatusDot" ||
key === "fileExplorerUnderlineFileName" ||
key === "unknownStatusIcon" ||
key === "unknownStatusLucideIcon" ||
key === "unknownStatusColor" ||
@ -112,21 +120,24 @@ export class FileExplorerIntegration implements IElementProcessor {
}
const noteStatusService = this.getFileNoteStatusService(dataPath);
const statuses = noteStatusService?.statuses ?? null;
const primaryStatus = getPrimaryStatus(statuses);
const hasStatus = Boolean(primaryStatus);
const fallbackColor = getUnknownStatusColor();
const statusColor = primaryStatus
? resolveStatusColor(primaryStatus, fallbackColor)
: undefined;
const textElement = textEl as HTMLElement;
const navItem = this.getNavItemElement(textElement);
this.applyFileNameColor(textElement, statusColor, hasStatus);
this.applyFileNameUnderline(textElement, statusColor, hasStatus);
this.applyStatusDot(textElement, statusColor, hasStatus);
this.applyFileBlockColor(navItem, statusColor, hasStatus);
this.applyLeftBorder(navItem, statusColor, hasStatus);
// Only render icons for markdown files
if (noteStatusService) {
this.applyFileNameColor(
textEl as HTMLElement,
noteStatusService.statuses,
);
this.applyFileBlockColor(
textEl as HTMLElement,
noteStatusService.statuses,
);
this.render(textEl, noteStatusService.statuses);
} else {
this.applyFileNameColor(textEl as HTMLElement, null);
this.applyFileBlockColor(textEl as HTMLElement, null);
}
}
}
@ -294,28 +305,18 @@ export class FileExplorerIntegration implements IElementProcessor {
private applyFileNameColor(
element?: HTMLElement | null,
statuses?: GroupedStatuses | null,
color?: string,
hasStatus?: boolean,
): void {
if (!element) {
return;
}
if (!settingsService.settings.fileExplorerColorFileName) {
this.clearFileNameColor(element);
return;
}
const primaryStatus = getPrimaryStatus(statuses);
if (!primaryStatus) {
this.clearFileNameColor(element);
return;
}
const color = resolveStatusColor(
primaryStatus,
getUnknownStatusColor(),
);
if (!color) {
if (
!settingsService.settings.fileExplorerColorFileName ||
!hasStatus ||
!color
) {
this.clearFileNameColor(element);
return;
}
@ -330,30 +331,19 @@ export class FileExplorerIntegration implements IElementProcessor {
}
private applyFileBlockColor(
element?: HTMLElement | null,
statuses?: GroupedStatuses | null,
navItem?: HTMLElement | null,
color?: string,
hasStatus?: boolean,
): void {
const navItem = this.getNavItemElement(element);
if (!navItem) {
return;
}
if (!settingsService.settings.fileExplorerColorBlock) {
this.clearFileBlockColor(navItem);
return;
}
const primaryStatus = getPrimaryStatus(statuses);
if (!primaryStatus) {
this.clearFileBlockColor(navItem);
return;
}
const color = resolveStatusColor(
primaryStatus,
getUnknownStatusColor(),
);
if (!color) {
if (
!settingsService.settings.fileExplorerColorBlock ||
!hasStatus ||
!color
) {
this.clearFileBlockColor(navItem);
return;
}
@ -362,6 +352,87 @@ export class FileExplorerIntegration implements IElementProcessor {
navItem.style.setProperty(this.FILE_BLOCK_COLOR_VAR, color);
}
private applyLeftBorder(
navItem?: HTMLElement | null,
color?: string,
hasStatus?: boolean,
): void {
if (!navItem) {
return;
}
if (
!settingsService.settings.fileExplorerLeftBorder ||
!hasStatus ||
!color
) {
this.clearFileLeftBorder(navItem);
return;
}
navItem.classList.add(this.FILE_BORDER_CLASS);
navItem.style.setProperty(this.FILE_BORDER_COLOR_VAR, color);
}
private applyFileNameUnderline(
element?: HTMLElement | null,
color?: string,
hasStatus?: boolean,
): void {
if (!element) {
return;
}
if (
!settingsService.settings.fileExplorerUnderlineFileName ||
!hasStatus ||
!color
) {
this.clearFileNameUnderline(element);
return;
}
element.classList.add(this.FILE_UNDERLINE_CLASS);
element.style.setProperty(this.FILE_UNDERLINE_COLOR_VAR, color);
}
private applyStatusDot(
element?: HTMLElement | null,
color?: string,
hasStatus?: boolean,
): void {
if (!element) {
return;
}
const existingDot = element.querySelector(
`.${this.STATUS_DOT_CLASS}`,
) as HTMLElement | null;
if (
!settingsService.settings.fileExplorerStatusDot ||
!hasStatus ||
!color
) {
if (existingDot) {
existingDot.remove();
}
return;
}
const dot =
existingDot ||
createSpan({
cls: this.STATUS_DOT_CLASS,
});
dot.setAttribute("aria-hidden", "true");
dot.style.backgroundColor = color;
if (!existingDot) {
element.appendChild(dot);
}
}
private getNavItemElement(
element?: HTMLElement | null,
): HTMLElement | null {
@ -388,8 +459,24 @@ export class FileExplorerIntegration implements IElementProcessor {
delete element.dataset[this.FILE_NAME_COLORIZED_ATTR];
}
private clearFileBlockColor(navItem: HTMLElement): void {
private clearFileBlockColor(navItem?: HTMLElement | null): void {
if (!navItem) {
return;
}
navItem.classList.remove(this.FILE_BLOCK_CLASS);
navItem.style.removeProperty(this.FILE_BLOCK_COLOR_VAR);
}
private clearFileLeftBorder(navItem?: HTMLElement | null): void {
if (!navItem) {
return;
}
navItem.classList.remove(this.FILE_BORDER_CLASS);
navItem.style.removeProperty(this.FILE_BORDER_COLOR_VAR);
}
private clearFileNameUnderline(element: HTMLElement): void {
element.classList.remove(this.FILE_UNDERLINE_CLASS);
element.style.removeProperty(this.FILE_UNDERLINE_COLOR_VAR);
}
}

File diff suppressed because one or more lines are too long

View file

@ -31,6 +31,42 @@
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: 0.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;

View file

@ -24,7 +24,10 @@ export type PluginSettings = {
showStatusIconsInExplorer: boolean;
hideUnknownStatusInExplorer: boolean;
fileExplorerColorFileName: boolean; // Whether to color the file explorer filename text using the current status color
fileExplorerColorBlock: boolean; // Whether to color the entire file explorer block background with the status color
fileExplorerColorBlock: boolean; // Whether to tint the entire explorer list item background using the status color
fileExplorerLeftBorder: boolean; // Whether to display a colored border on the explorer item
fileExplorerStatusDot: boolean; // Whether to append a small colored dot next to the filename
fileExplorerUnderlineFileName: boolean; // Whether to underline the filename using the status color
enabledTemplates: string[]; // IDs of enabled templates
useCustomStatusesOnly: boolean; // Whether to use only custom statuses or include templates
useMultipleStatuses: boolean; // Whether to allow multiple statuses per note