mirror of
https://github.com/devonthesofa/obsidian-note-status.git
synced 2026-07-22 12:30:24 +00:00
feat: Minmal design
This commit is contained in:
parent
ed88c56b8e
commit
4299fc15d6
3 changed files with 395 additions and 290 deletions
10
main.ts
10
main.ts
|
|
@ -147,6 +147,11 @@ export default class NoteStatus extends Plugin {
|
|||
this.statusDropdown = new StatusDropdown(this.app, this.settings, this.statusService);
|
||||
this.explorerIntegration = new ExplorerIntegration(this.app, this.settings, this.statusService);
|
||||
this.statusContextMenu = new StatusContextMenu(this.app, this.settings, this.statusService);
|
||||
|
||||
// Register event to update toolbar button when plugin is loaded
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
this.statusDropdown.update(this.getCurrentStatuses());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -369,6 +374,7 @@ export default class NoteStatus extends Plugin {
|
|||
} else {
|
||||
this.lastActiveFile = null;
|
||||
this.statusBar.update(['unknown']);
|
||||
this.statusDropdown.update(['unknown']); // Add this line to update toolbar when no file is open
|
||||
}
|
||||
}));
|
||||
|
||||
|
|
@ -460,11 +466,13 @@ export default class NoteStatus extends Plugin {
|
|||
const activeFile = this.app.workspace.getActiveFile();
|
||||
if (!activeFile || activeFile.extension !== 'md') {
|
||||
this.statusBar.update(['unknown']);
|
||||
this.statusDropdown.update(['unknown']); // Add this line to update toolbar
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const statuses = this.statusService.getFileStatuses(activeFile);
|
||||
this.statusBar.update(statuses);
|
||||
this.statusDropdown.update(statuses); // Add this line to update toolbar
|
||||
} catch (error) {
|
||||
console.error('Error checking note status:', error);
|
||||
if (!this.hasShownErrorNotification) {
|
||||
|
|
|
|||
163
styles.css
163
styles.css
|
|
@ -985,4 +985,167 @@
|
|||
.note-status-pane {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Toolbar button styling */
|
||||
.note-status-toolbar-button-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.note-status-toolbar-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: var(--radius-s, 4px);
|
||||
color: var(--text-normal);
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s var(--ease-out);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.note-status-toolbar-button:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
.note-status-toolbar-badge-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.note-status-toolbar-icon {
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.note-status-count-badge {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -8px;
|
||||
background-color: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border-radius: 10px;
|
||||
font-size: 10px;
|
||||
min-width: 14px;
|
||||
height: 14px;
|
||||
padding: 0 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Toolbar popover styling */
|
||||
.note-status-toolbar-popover {
|
||||
width: 280px;
|
||||
max-width: 90vw;
|
||||
border-radius: var(--radius-m, 8px);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-m), 0 0 15px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
background: var(--background-primary);
|
||||
}
|
||||
|
||||
.note-status-popover-header {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
background: var(--background-secondary-alt);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.note-status-popover-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.note-status-popover-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
.note-status-popover-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 12px;
|
||||
background: var(--background-primary-alt);
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
/* Empty indicator for popover */
|
||||
.note-status-popover-chips .note-status-empty-indicator {
|
||||
width: 100%;
|
||||
min-height: auto;
|
||||
padding: 8px 0;
|
||||
justify-content: flex-start;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Ensure the button container has proper z-index and positioning */
|
||||
.note-status-toolbar-button-container {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
margin-right: 4px !important;
|
||||
position: relative !important;
|
||||
z-index: 10 !important;
|
||||
}
|
||||
|
||||
/* Ensure button is visible with proper sizing */
|
||||
.note-status-toolbar-button {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
border-radius: var(--radius-s, 4px) !important;
|
||||
color: var(--text-normal) !important;
|
||||
padding: 6px !important;
|
||||
cursor: pointer !important;
|
||||
transition: all 0.15s var(--ease-out) !important;
|
||||
position: relative !important;
|
||||
width: 28px !important;
|
||||
height: 28px !important;
|
||||
min-width: 28px !important;
|
||||
min-height: 28px !important;
|
||||
}
|
||||
|
||||
/* Make icons more visible */
|
||||
.note-status-toolbar-icon {
|
||||
font-size: 16px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
/* Alternative selectors for toolbar button placement */
|
||||
.view-header .view-actions .note-status-toolbar-button-container,
|
||||
.view-header-actions .note-status-toolbar-button-container,
|
||||
.workspace-ribbon.mod-right .note-status-toolbar-button-container {
|
||||
display: flex !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* Debug outline to help identify the button (you can remove this after debugging) */
|
||||
.note-status-toolbar-button-container {
|
||||
outline: 1px solid rgba(255, 0, 0, 0.3) !important;
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import { NoteStatusSettings, Status } from '../models/types';
|
|||
import { StatusService } from '../services/status-service';
|
||||
|
||||
/**
|
||||
* Enhanced status dropdown with improved UI/UX and touch support
|
||||
* Enhanced status dropdown with toolbar integration
|
||||
*/
|
||||
export class StatusDropdown {
|
||||
private app: any;
|
||||
|
|
@ -15,6 +15,8 @@ export class StatusDropdown {
|
|||
private statusPopover?: HTMLElement;
|
||||
private isPopoverOpen = false;
|
||||
private clickOutsideHandler: any;
|
||||
private toolbarButtonContainer?: HTMLElement;
|
||||
private toolbarButton?: HTMLElement;
|
||||
|
||||
// Animation timings
|
||||
private readonly ANIMATION_DURATION = 220;
|
||||
|
|
@ -26,6 +28,118 @@ export class StatusDropdown {
|
|||
|
||||
// Bind methods to preserve this context
|
||||
this.handleClickOutside = this.handleClickOutside.bind(this);
|
||||
|
||||
// Initialize toolbar button after layout is ready
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
this.initToolbarButton();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the toolbar button in the Obsidian ribbon
|
||||
*/
|
||||
private initToolbarButton(): void {
|
||||
// Wait for next tick to ensure the UI is fully rendered
|
||||
setTimeout(() => {
|
||||
// Try different selectors to find the toolbar
|
||||
const toolbarContainer = document.querySelector('.view-header-actions') ||
|
||||
document.querySelector('.view-actions') ||
|
||||
document.querySelector('.workspace-ribbon.mod-right');
|
||||
|
||||
if (!toolbarContainer) {
|
||||
console.error('Note Status: Could not find toolbar container');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create button container for proper positioning
|
||||
this.toolbarButtonContainer = document.createElement('div');
|
||||
this.toolbarButtonContainer.addClass('note-status-toolbar-button-container');
|
||||
|
||||
// Create the button element
|
||||
this.toolbarButton = document.createElement('button');
|
||||
this.toolbarButton.addClass('note-status-toolbar-button', 'clickable-icon');
|
||||
this.toolbarButton.setAttribute('aria-label', 'Note Status');
|
||||
|
||||
// Update initial button state
|
||||
this.updateToolbarButton();
|
||||
|
||||
// Add click handler
|
||||
this.toolbarButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.toggleStatusPopover(this.toolbarButton);
|
||||
});
|
||||
|
||||
// Add the button to the container
|
||||
this.toolbarButtonContainer.appendChild(this.toolbarButton);
|
||||
|
||||
// Insert at specific position - before the last element (which is usually the more options button)
|
||||
// If there's no children or insertion fails, just append it
|
||||
try {
|
||||
if (toolbarContainer.children.length > 0) {
|
||||
// Insert before the last element (more options button)
|
||||
toolbarContainer.insertBefore(
|
||||
this.toolbarButtonContainer,
|
||||
toolbarContainer.children[toolbarContainer.children.length - 1]
|
||||
);
|
||||
} else {
|
||||
toolbarContainer.appendChild(this.toolbarButtonContainer);
|
||||
}
|
||||
|
||||
console.log('Note Status: Toolbar button added successfully');
|
||||
} catch (error) {
|
||||
console.error('Note Status: Error inserting toolbar button', error);
|
||||
// Fallback - just append it
|
||||
toolbarContainer.appendChild(this.toolbarButtonContainer);
|
||||
}
|
||||
}, 500); // Waiting 500ms to ensure the UI is ready
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the toolbar button appearance based on current statuses
|
||||
*/
|
||||
private updateToolbarButton(): void {
|
||||
if (!this.toolbarButton) return;
|
||||
|
||||
// Clear existing content
|
||||
this.toolbarButton.empty();
|
||||
|
||||
// Check if we have a valid status
|
||||
const hasValidStatus = this.currentStatuses.length > 0 &&
|
||||
!this.currentStatuses.every(status => status === 'unknown');
|
||||
|
||||
// Create badge container
|
||||
const badgeContainer = document.createElement('div');
|
||||
badgeContainer.addClass('note-status-toolbar-badge-container');
|
||||
|
||||
if (hasValidStatus) {
|
||||
// Show primary status icon and indicator for multiple statuses
|
||||
const primaryStatus = this.currentStatuses[0];
|
||||
const statusInfo = this.statusService.getAllStatuses().find(s => s.name === primaryStatus);
|
||||
|
||||
if (statusInfo) {
|
||||
// Primary status icon
|
||||
const iconSpan = document.createElement('span');
|
||||
iconSpan.addClass(`note-status-toolbar-icon`, `status-${primaryStatus}`);
|
||||
iconSpan.textContent = statusInfo.icon;
|
||||
badgeContainer.appendChild(iconSpan);
|
||||
|
||||
// Add count indicator if multiple statuses
|
||||
if (this.settings.useMultipleStatuses && this.currentStatuses.length > 1) {
|
||||
const countBadge = document.createElement('span');
|
||||
countBadge.addClass('note-status-count-badge');
|
||||
countBadge.textContent = `+${this.currentStatuses.length - 1}`;
|
||||
badgeContainer.appendChild(countBadge);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Show default status icon
|
||||
const iconSpan = document.createElement('span');
|
||||
iconSpan.addClass('note-status-toolbar-icon', 'status-unknown');
|
||||
iconSpan.textContent = '📌'; // Default tag icon
|
||||
badgeContainer.appendChild(iconSpan);
|
||||
}
|
||||
|
||||
this.toolbarButton.appendChild(badgeContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -39,8 +153,13 @@ export class StatusDropdown {
|
|||
this.currentStatuses = [...currentStatuses]; // Create a copy to ensure it's updated
|
||||
}
|
||||
|
||||
// Clear and render fresh dropdown - force a complete UI rebuild
|
||||
this.render();
|
||||
// Update toolbar button
|
||||
this.updateToolbarButton();
|
||||
|
||||
// Clear and render fresh dropdown if it exists
|
||||
if (this.dropdownContainer) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -48,172 +167,78 @@ export class StatusDropdown {
|
|||
*/
|
||||
public updateSettings(settings: NoteStatusSettings): void {
|
||||
this.settings = settings;
|
||||
this.render();
|
||||
this.updateToolbarButton();
|
||||
if (this.dropdownContainer) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render or remove the dropdown based on settings
|
||||
*/
|
||||
public render(): void {
|
||||
// Get current active view
|
||||
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
|
||||
// Find all existing dropdowns and remove them
|
||||
document.querySelectorAll('.note-status-dropdown').forEach(el => el.remove());
|
||||
this.dropdownContainer = undefined;
|
||||
|
||||
// If setting is turned off or no active markdown view, don't render
|
||||
if (!this.settings.showStatusDropdown || !view) {
|
||||
// Remove existing popover if open
|
||||
if (this.isPopoverOpen) {
|
||||
this.closeStatusPopover();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the status popover
|
||||
*/
|
||||
private toggleStatusPopover(targetEl?: HTMLElement): void {
|
||||
if (this.isPopoverOpen) {
|
||||
this.closeStatusPopover();
|
||||
} else {
|
||||
this.openStatusPopover(targetEl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the status selection popover
|
||||
* @param targetEl - The element that triggered the popover (for positioning)
|
||||
*/
|
||||
private openStatusPopover(targetEl?: HTMLElement): void {
|
||||
if (this.isPopoverOpen) {
|
||||
this.closeStatusPopover();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get container for the dropdown
|
||||
const contentEl = view.contentEl;
|
||||
|
||||
// Create new dropdown container
|
||||
const dropdownEl = document.createElement('div');
|
||||
dropdownEl.addClass('note-status-dropdown', this.settings.dropdownPosition);
|
||||
this.isPopoverOpen = true;
|
||||
|
||||
// Add animation class
|
||||
dropdownEl.addClass('note-status-animate-in');
|
||||
// Create popover element
|
||||
this.statusPopover = document.createElement('div');
|
||||
this.statusPopover.addClass('note-status-popover', 'note-status-toolbar-popover');
|
||||
document.body.appendChild(this.statusPopover);
|
||||
|
||||
// Insert at correct position
|
||||
if (this.settings.dropdownPosition === 'top') {
|
||||
contentEl.insertBefore(dropdownEl, contentEl.firstChild);
|
||||
} else {
|
||||
contentEl.appendChild(dropdownEl);
|
||||
}
|
||||
|
||||
// Store reference
|
||||
this.dropdownContainer = dropdownEl;
|
||||
|
||||
// Create header with label and toggle button
|
||||
this.createDropdownHeader();
|
||||
// Create header
|
||||
const headerEl = this.statusPopover.createDiv({ cls: 'note-status-popover-header' });
|
||||
|
||||
// Create chips display for current statuses
|
||||
this.createStatusChips();
|
||||
|
||||
// Add "Add Status" button that shows popover
|
||||
this.createAddStatusButton();
|
||||
|
||||
// Remove animation class after animation completes
|
||||
setTimeout(() => {
|
||||
dropdownEl.removeClass('note-status-animate-in');
|
||||
}, this.ANIMATION_DURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the dropdown header with label and control buttons
|
||||
*/
|
||||
private createDropdownHeader(): void {
|
||||
if (!this.dropdownContainer) return;
|
||||
|
||||
const headerEl = this.dropdownContainer.createDiv({ cls: 'note-status-dropdown-header' });
|
||||
|
||||
// Create title with icon
|
||||
const titleEl = headerEl.createDiv({ cls: 'note-status-dropdown-title' });
|
||||
const iconContainer = titleEl.createDiv({ cls: 'note-status-dropdown-icon' });
|
||||
// Title with icon
|
||||
const titleEl = headerEl.createDiv({ cls: 'note-status-popover-title' });
|
||||
const iconContainer = titleEl.createDiv({ cls: 'note-status-popover-icon' });
|
||||
setIcon(iconContainer, 'tag');
|
||||
titleEl.createSpan({ text: 'Note Status', cls: 'note-status-dropdown-label' });
|
||||
titleEl.createSpan({ text: 'Note Status', cls: 'note-status-popover-label' });
|
||||
|
||||
// Add action buttons
|
||||
const actionsEl = headerEl.createDiv({ cls: 'note-status-dropdown-actions' });
|
||||
|
||||
// Toggle multi/single mode button
|
||||
const toggleModeButton = actionsEl.createEl('button', {
|
||||
cls: 'note-status-action-button',
|
||||
attr: {
|
||||
'aria-label': this.settings.useMultipleStatuses ? 'Switch to single status mode' : 'Switch to multiple statuses mode',
|
||||
'title': this.settings.useMultipleStatuses ? 'Switch to single status mode' : 'Switch to multiple statuses mode'
|
||||
}
|
||||
});
|
||||
setIcon(toggleModeButton, this.settings.useMultipleStatuses ? 'list-minus' : 'list-plus');
|
||||
|
||||
toggleModeButton.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// Add animation
|
||||
toggleModeButton.addClass('note-status-button-active');
|
||||
|
||||
// Update setting
|
||||
this.settings.useMultipleStatuses = !this.settings.useMultipleStatuses;
|
||||
|
||||
// Trigger settings save
|
||||
window.dispatchEvent(new CustomEvent('note-status:settings-changed'));
|
||||
|
||||
// Re-render the dropdown
|
||||
setTimeout(() => {
|
||||
this.render();
|
||||
new Notice(`Switched to ${this.settings.useMultipleStatuses ? 'multiple' : 'single'} status mode`);
|
||||
}, 150);
|
||||
});
|
||||
|
||||
// Close button
|
||||
const closeButton = actionsEl.createEl('button', {
|
||||
cls: 'note-status-action-button',
|
||||
attr: {
|
||||
'aria-label': 'Hide status bar',
|
||||
'title': 'Hide status bar'
|
||||
}
|
||||
});
|
||||
setIcon(closeButton, 'x');
|
||||
|
||||
closeButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// Add animation
|
||||
this.dropdownContainer?.addClass('note-status-animate-out');
|
||||
|
||||
setTimeout(() => {
|
||||
this.settings.showStatusDropdown = false;
|
||||
this.render();
|
||||
|
||||
// Trigger settings save
|
||||
window.dispatchEvent(new CustomEvent('note-status:settings-changed'));
|
||||
|
||||
new Notice('Status dropdown hidden');
|
||||
}, this.ANIMATION_DURATION);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the chips display for current statuses
|
||||
*/
|
||||
private createStatusChips(): void {
|
||||
if (!this.dropdownContainer) return;
|
||||
|
||||
// Make sure previous container is removed
|
||||
if (this.statusChipsContainer) {
|
||||
this.statusChipsContainer.remove();
|
||||
}
|
||||
|
||||
// Create container for status chips
|
||||
this.statusChipsContainer = this.dropdownContainer.createDiv({
|
||||
cls: 'note-status-chips-container'
|
||||
});
|
||||
// Current status chips
|
||||
const chipsContainer = this.statusPopover.createDiv({ cls: 'note-status-popover-chips' });
|
||||
|
||||
// Show 'No status' indicator if no statuses or only unknown status
|
||||
if (this.currentStatuses.length === 0 ||
|
||||
(this.currentStatuses.length === 1 && this.currentStatuses[0] === 'unknown')) {
|
||||
if (this.statusChipsContainer) {
|
||||
this.statusChipsContainer.createDiv({
|
||||
cls: 'note-status-empty-indicator',
|
||||
text: 'No status assigned'
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Add chip for each status
|
||||
if (this.statusChipsContainer) {
|
||||
chipsContainer.createDiv({
|
||||
cls: 'note-status-empty-indicator',
|
||||
text: 'No status assigned'
|
||||
});
|
||||
} else {
|
||||
// Add chip for each status
|
||||
this.currentStatuses.forEach(status => {
|
||||
if (status === 'unknown') return; // Skip unknown status
|
||||
|
||||
const statusObj = this.statusService.getAllStatuses().find(s => s.name === status);
|
||||
if (!statusObj) return;
|
||||
if (!this.statusChipsContainer) return;
|
||||
|
||||
const chipEl = this.statusChipsContainer.createDiv({
|
||||
const chipEl = chipsContainer.createDiv({
|
||||
cls: `note-status-chip status-${status}`
|
||||
});
|
||||
|
||||
|
|
@ -254,6 +279,15 @@ export class StatusDropdown {
|
|||
// Get updated statuses
|
||||
const updatedStatuses = this.statusService.getFileStatuses(this.app.workspace.getActiveFile());
|
||||
|
||||
// Update current statuses
|
||||
this.currentStatuses = updatedStatuses;
|
||||
|
||||
// Update toolbar button
|
||||
this.updateToolbarButton();
|
||||
|
||||
// Refresh chips
|
||||
this.openStatusPopover(targetEl);
|
||||
|
||||
// Dispatch event for UI update
|
||||
window.dispatchEvent(new CustomEvent('note-status:status-changed', {
|
||||
detail: { statuses: updatedStatuses }
|
||||
|
|
@ -261,65 +295,8 @@ export class StatusDropdown {
|
|||
}, 150);
|
||||
});
|
||||
}
|
||||
|
||||
// Make the whole chip clickable if in single status mode
|
||||
if (!this.settings.useMultipleStatuses) {
|
||||
chipEl.addClass('clickable');
|
||||
chipEl.addEventListener('click', (e) => {
|
||||
this.openStatusPopover(chipEl);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the add status button
|
||||
*/
|
||||
private createAddStatusButton(): void {
|
||||
if (!this.dropdownContainer) return;
|
||||
|
||||
// Only show add button if using multiple statuses or no status is set
|
||||
if (!this.settings.useMultipleStatuses &&
|
||||
this.currentStatuses.length > 0 &&
|
||||
this.currentStatuses[0] !== 'unknown') {
|
||||
return;
|
||||
}
|
||||
|
||||
const addButton = this.dropdownContainer.createDiv({
|
||||
cls: 'note-status-add-button',
|
||||
attr: {
|
||||
'aria-label': 'Add status',
|
||||
'title': 'Add status'
|
||||
}
|
||||
});
|
||||
|
||||
const iconContainer = addButton.createSpan({ cls: 'note-status-add-icon' });
|
||||
setIcon(iconContainer, 'plus-circle');
|
||||
addButton.createSpan({ text: 'Add status', cls: 'note-status-add-text' });
|
||||
|
||||
// Add click handler to open status popover
|
||||
addButton.addEventListener('click', (e) => {
|
||||
this.openStatusPopover(addButton);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the status selection popover
|
||||
* @param targetEl - The element that triggered the popover (for positioning)
|
||||
*/
|
||||
private openStatusPopover(targetEl?: HTMLElement): void {
|
||||
if (this.isPopoverOpen) {
|
||||
this.closeStatusPopover();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isPopoverOpen = true;
|
||||
|
||||
// Create popover element
|
||||
if (!this.dropdownContainer) return;
|
||||
|
||||
this.statusPopover = this.dropdownContainer.createDiv({ cls: 'note-status-popover' });
|
||||
|
||||
// Create search filter
|
||||
const searchContainer = this.statusPopover.createDiv({ cls: 'note-status-popover-search' });
|
||||
|
|
@ -387,42 +364,33 @@ export class StatusDropdown {
|
|||
// Add selection animation
|
||||
optionEl.addClass('note-status-option-selecting');
|
||||
|
||||
// First close the popover if in single status mode to improve perceived performance
|
||||
if (!this.settings.useMultipleStatuses) {
|
||||
this.closeStatusPopover();
|
||||
}
|
||||
|
||||
// Apply status changes after brief delay for animation
|
||||
setTimeout(async () => {
|
||||
if (this.settings.useMultipleStatuses) {
|
||||
await this.statusService.toggleNoteStatus(status.name);
|
||||
} else {
|
||||
await this.statusService.updateNoteStatuses([status.name]);
|
||||
// Close popover in single status mode
|
||||
this.closeStatusPopover();
|
||||
}
|
||||
|
||||
// Get fresh status from file
|
||||
const freshStatuses = this.statusService.getFileStatuses(activeFile);
|
||||
|
||||
// Force-update both the current instance and trigger global update
|
||||
// Update dropdown display first
|
||||
// Update current statuses and toolbar button
|
||||
this.currentStatuses = [...freshStatuses];
|
||||
this.render();
|
||||
this.updateToolbarButton();
|
||||
|
||||
// Then notify other components like status bar
|
||||
// Refresh status options if popover still open
|
||||
if (this.isPopoverOpen && this.settings.useMultipleStatuses) {
|
||||
populateOptions(searchInput.value);
|
||||
}
|
||||
|
||||
// Dispatch events for UI update
|
||||
window.dispatchEvent(new CustomEvent('note-status:status-changed', {
|
||||
detail: { statuses: freshStatuses }
|
||||
}));
|
||||
|
||||
// Also dispatch the refresh-ui event to update the status panel
|
||||
window.dispatchEvent(new CustomEvent('note-status:refresh-ui'));
|
||||
|
||||
// Keep popover open for multi-select mode
|
||||
if (this.settings.useMultipleStatuses) {
|
||||
// Re-open the popover to maintain multi-select mode
|
||||
setTimeout(() => {
|
||||
this.openStatusPopover(targetEl);
|
||||
}, 10);
|
||||
}
|
||||
}, 150);
|
||||
} catch (error) {
|
||||
console.error('Error updating status:', error);
|
||||
|
|
@ -440,8 +408,8 @@ export class StatusDropdown {
|
|||
populateOptions(searchInput.value);
|
||||
});
|
||||
|
||||
// Position the popover relative to the target element
|
||||
this.positionPopover(targetEl);
|
||||
// Position the popover relative to the toolbar button
|
||||
this.positionToolbarPopover(targetEl);
|
||||
|
||||
// Add animation class
|
||||
this.statusPopover.addClass('note-status-popover-animate-in');
|
||||
|
|
@ -462,69 +430,25 @@ export class StatusDropdown {
|
|||
}
|
||||
|
||||
/**
|
||||
* Position the popover relative to the target element
|
||||
* Position the popover relative to the toolbar button
|
||||
*/
|
||||
private positionPopover(targetEl?: HTMLElement): void {
|
||||
if (!this.statusPopover || !this.dropdownContainer) return;
|
||||
private positionToolbarPopover(targetEl?: HTMLElement): void {
|
||||
if (!this.statusPopover || !targetEl) return;
|
||||
|
||||
// Reset positioning
|
||||
this.statusPopover.style.position = 'absolute';
|
||||
this.statusPopover.style.top = '';
|
||||
this.statusPopover.style.bottom = '';
|
||||
this.statusPopover.style.left = '';
|
||||
this.statusPopover.style.right = '';
|
||||
this.statusPopover.style.transform = '';
|
||||
this.statusPopover.style.position = 'fixed';
|
||||
this.statusPopover.style.zIndex = '999';
|
||||
|
||||
if (targetEl) {
|
||||
// Get target element's position
|
||||
const targetRect = targetEl.getBoundingClientRect();
|
||||
const containerRect = this.dropdownContainer.getBoundingClientRect();
|
||||
|
||||
// Calculate popover position relative to the dropdown container
|
||||
const top = targetRect.bottom - containerRect.top;
|
||||
const left = targetRect.left - containerRect.left;
|
||||
|
||||
// Position popover above or below target element based on available space
|
||||
const viewportHeight = window.innerHeight;
|
||||
const spaceBelow = viewportHeight - targetRect.bottom;
|
||||
const availableStatuses = this.statusService.getAllStatuses().filter(s => s.name !== 'unknown');
|
||||
const popoverHeight = Math.min(300, availableStatuses.length * 40 + 60); // Estimate popover height
|
||||
|
||||
if (spaceBelow < popoverHeight && targetRect.top > popoverHeight) {
|
||||
// Position above if not enough space below but enough space above
|
||||
this.statusPopover.style.bottom = `${containerRect.bottom - targetRect.top}px`;
|
||||
this.statusPopover.style.maxHeight = `${targetRect.top - 20}px`;
|
||||
this.statusPopover.classList.add('popover-top');
|
||||
this.statusPopover.classList.remove('popover-bottom');
|
||||
} else {
|
||||
// Position below (default)
|
||||
this.statusPopover.style.top = `${top}px`;
|
||||
this.statusPopover.style.maxHeight = `${viewportHeight - targetRect.bottom - 20}px`;
|
||||
this.statusPopover.classList.add('popover-bottom');
|
||||
this.statusPopover.classList.remove('popover-top');
|
||||
}
|
||||
|
||||
// Center horizontally relative to target
|
||||
const targetWidth = targetRect.width;
|
||||
const popoverWidth = Math.min(250, targetWidth * 1.5);
|
||||
this.statusPopover.style.width = `${popoverWidth}px`;
|
||||
this.statusPopover.style.left = `${left + (targetWidth / 2) - (popoverWidth / 2)}px`;
|
||||
|
||||
// Ensure popover doesn't go out of container bounds
|
||||
const leftBound = parseInt(this.statusPopover.style.left);
|
||||
if (leftBound < 10) {
|
||||
this.statusPopover.style.left = '10px';
|
||||
} else if (leftBound + popoverWidth > containerRect.width - 10) {
|
||||
this.statusPopover.style.left = `${containerRect.width - popoverWidth - 10}px`;
|
||||
}
|
||||
} else {
|
||||
// Default positioning if no target element
|
||||
this.statusPopover.style.top = '100%';
|
||||
this.statusPopover.style.left = '50%';
|
||||
this.statusPopover.style.transform = 'translateX(-50%)';
|
||||
this.statusPopover.style.width = '250px';
|
||||
this.statusPopover.classList.add('popover-bottom');
|
||||
}
|
||||
// Get target element's position
|
||||
const targetRect = targetEl.getBoundingClientRect();
|
||||
|
||||
// Position below the button
|
||||
this.statusPopover.style.top = `${targetRect.bottom + 5}px`;
|
||||
this.statusPopover.style.right = `${window.innerWidth - targetRect.right}px`;
|
||||
|
||||
// Set max height based on viewport
|
||||
const maxHeight = window.innerHeight - targetRect.bottom - 40;
|
||||
this.statusPopover.style.maxHeight = `${maxHeight}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -540,7 +464,8 @@ export class StatusDropdown {
|
|||
* Handle click outside the popover
|
||||
*/
|
||||
private handleClickOutside(e: MouseEvent) {
|
||||
if (this.statusPopover && !this.statusPopover.contains(e.target as Node)) {
|
||||
if (this.statusPopover && !this.statusPopover.contains(e.target as Node) &&
|
||||
this.toolbarButton && !this.toolbarButton.contains(e.target as Node)) {
|
||||
this.closeStatusPopover();
|
||||
}
|
||||
}
|
||||
|
|
@ -643,6 +568,10 @@ export class StatusDropdown {
|
|||
// Get updated statuses
|
||||
const updatedStatuses = this.statusService.getFileStatuses(activeFile);
|
||||
|
||||
// Update current statuses and toolbar button
|
||||
this.currentStatuses = updatedStatuses;
|
||||
this.updateToolbarButton();
|
||||
|
||||
// Dispatch events for UI update
|
||||
window.dispatchEvent(new CustomEvent('note-status:status-changed', {
|
||||
detail: { statuses: updatedStatuses }
|
||||
|
|
@ -654,6 +583,10 @@ export class StatusDropdown {
|
|||
item.onClick(async () => {
|
||||
await this.statusService.updateNoteStatus(status.name);
|
||||
|
||||
// Update current statuses and toolbar button
|
||||
this.currentStatuses = [status.name];
|
||||
this.updateToolbarButton();
|
||||
|
||||
// Dispatch events for UI update
|
||||
window.dispatchEvent(new CustomEvent('note-status:status-changed', {
|
||||
detail: { statuses: [status.name] }
|
||||
|
|
@ -698,12 +631,13 @@ export class StatusDropdown {
|
|||
* Remove dropdown when plugin is unloaded
|
||||
*/
|
||||
public unload(): void {
|
||||
if (this.dropdownContainer) {
|
||||
this.dropdownContainer.remove();
|
||||
this.dropdownContainer = undefined;
|
||||
}
|
||||
|
||||
// Remove any popover and event listeners
|
||||
// Close any open popover
|
||||
this.closeStatusPopover();
|
||||
|
||||
// Remove toolbar button
|
||||
if (this.toolbarButtonContainer) {
|
||||
this.toolbarButtonContainer.remove();
|
||||
this.toolbarButtonContainer = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue