From 75f046ec9fda20643ae0dabf17cd824af9f9ef2b Mon Sep 17 00:00:00 2001 From: Hoang Nam Date: Mon, 17 Mar 2025 02:20:48 +0700 Subject: [PATCH] fix: add delays and improve file content handling --- main.ts | 1447 ++++++++++++++++++++++++++++++++++------------------ styles.css | 5 +- 2 files changed, 945 insertions(+), 507 deletions(-) diff --git a/main.ts b/main.ts index b37cddc..4afd331 100644 --- a/main.ts +++ b/main.ts @@ -1,8 +1,25 @@ -import { App, Plugin, PluginSettingTab, Setting, WorkspaceLeaf, TFile, MarkdownView, ItemView, Editor, MarkdownPostProcessorContext, debounce, Notice } from 'obsidian'; +import { + App, + Plugin, + PluginSettingTab, + Setting, + WorkspaceLeaf, + TFile, + MarkdownView, + ItemView, + Editor, + MarkdownPostProcessorContext, + debounce, + Notice, +} from "obsidian"; // Định nghĩa interface cho DataviewAPI interface DataviewApi { - executeJs(code: string, container: HTMLElement, sourcePath?: string): Promise; + executeJs( + code: string, + container: HTMLElement, + sourcePath?: string + ): Promise; page(path: string): any; pages(source: string): any[]; // Không sử dụng eval vì có thể không tồn tại trong một số phiên bản Dataview @@ -22,29 +39,29 @@ function getDataviewAPI(app: App): DataviewApi | null { if (window.DataviewAPI) { return window.DataviewAPI; } - + // Cách 2: Thông qua app.plugins // @ts-ignore const dataviewPlugin = app.plugins?.plugins?.dataview; if (dataviewPlugin && dataviewPlugin.api) { return dataviewPlugin.api; } - + // Cách 3: Kiểm tra xem plugin có được enable không // @ts-ignore - if (app.plugins.enabledPlugins.has('dataview')) { - console.log('Dataview plugin is enabled but API is not available yet'); + if (app.plugins.enabledPlugins.has("dataview")) { + console.log("Dataview plugin is enabled but API is not available yet"); return null; } - - console.log('Dataview plugin is not enabled'); + + console.log("Dataview plugin is not enabled"); return null; } interface TaskProgressBarSettings { mySetting: string; showDebugInfo: boolean; - progressColorScheme: 'default' | 'red-orange-green' | 'custom'; + progressColorScheme: "default" | "red-orange-green" | "custom"; lowProgressColor: string; mediumProgressColor: string; highProgressColor: string; @@ -57,45 +74,50 @@ interface TaskProgressBarSettings { editorChangeDelay: number; // Add editor change delay keyboardInputDelay: number; // Add keyboard input delay checkboxClickDelay: number; // Add checkbox click delay + maxTabsHeight: string; // New setting for max-height of workspace tabs } const DEFAULT_SETTINGS: TaskProgressBarSettings = { - mySetting: 'default', + mySetting: "default", showDebugInfo: false, - progressColorScheme: 'default', - lowProgressColor: '#e06c75', // Red - mediumProgressColor: '#e5c07b', // Orange/Yellow - highProgressColor: '#61afef', // Blue - completeProgressColor: '#98c379', // Green + progressColorScheme: "default", + lowProgressColor: "#e06c75", // Red + mediumProgressColor: "#e5c07b", // Orange/Yellow + highProgressColor: "#61afef", // Blue + completeProgressColor: "#98c379", // Green lowProgressThreshold: 33, mediumProgressThreshold: 66, highProgressThreshold: 99, showUpdateAnimation: true, // Default to true to maintain current behavior updateAnimationDelay: 300, // Default animation delay (300ms) editorChangeDelay: 500, // Default editor change delay (500ms) - keyboardInputDelay: 10, // Default keyboard input delay (10ms) - checkboxClickDelay: 50 // Default checkbox click delay (50ms) -} + keyboardInputDelay: 100, // Default keyboard input delay (10ms) + checkboxClickDelay: 200, // Default checkbox click delay (50ms) + maxTabsHeight: "auto" // Default to "auto" +}; export default class TaskProgressBarPlugin extends Plugin { settings: TaskProgressBarSettings; dvAPI: DataviewApi | null = null; sidebarView: TaskProgressBarView | null = null; private lastActiveFile: TFile | null = null; - private lastFileContent: string = ''; + private lastFileContent: string = ""; private dataviewCheckInterval: number | null = null; async onload() { await this.loadSettings(); + // Apply the max-height CSS style as soon as the plugin loads + this.applyMaxTabsHeightStyle(); + // Register view type for the sidebar this.registerView( - 'progress-tracker', + "progress-tracker", (leaf) => (this.sidebarView = new TaskProgressBarView(leaf, this)) ); // Add icon to the left sidebar - this.addRibbonIcon('bar-chart-horizontal', 'Progress Tracker', () => { + this.addRibbonIcon("bar-chart-horizontal", "Progress Tracker", () => { this.activateView(); }); @@ -104,15 +126,26 @@ export default class TaskProgressBarPlugin extends Plugin { // Kiểm tra Dataview API và thiết lập interval để kiểm tra lại nếu chưa có this.checkDataviewAPI(); - + // Register event to update progress bar when file changes this.registerEvent( - this.app.workspace.on('file-open', (file) => { + this.app.workspace.on("file-open", (file) => { if (file) { this.lastActiveFile = file; - this.updateLastFileContent(file); - if (this.sidebarView) { - this.sidebarView.updateProgressBar(file); + + // Only update if we haven't updated this file recently + // This prevents multiple updates in quick succession + const now = Date.now(); + const lastUpdate = + this.sidebarView?.lastFileUpdateMap.get(file.path) || 0; + if (now - lastUpdate > 2000) { + // Only update if it's been more than 2 seconds since last update for this file + setTimeout(async () => { + await this.updateLastFileContent(file); + if (this.sidebarView) { + this.sidebarView.updateProgressBar(file); + } + }, 300); } } }) @@ -120,83 +153,138 @@ export default class TaskProgressBarPlugin extends Plugin { // Register event to update progress bar when editor changes this.registerEvent( - this.app.workspace.on('editor-change', debounce(async (editor, view) => { - if (view instanceof MarkdownView && this.sidebarView) { - // Lấy nội dung hiện tại của editor - const content = editor.getValue(); - - // Kiểm tra nếu nội dung thay đổi và có chứa task hoặc nội dung cũ có chứa task - if (content.includes('- [') || content.includes('- [ ]') || content.includes('- [x]') || - this.lastFileContent.includes('- [') || this.lastFileContent.includes('- [ ]') || this.lastFileContent.includes('- [x]')) { - // Cập nhật ngay lập tức - if (this.lastActiveFile) { - // Cập nhật nội dung file cuối cùng trước khi kiểm tra thay đổi - this.lastActiveFile = view.file; - - // Cập nhật thanh tiến trình ngay lập tức - this.sidebarView.updateProgressBar(view.file, content); - - // Sau đó mới cập nhật nội dung file cuối cùng - this.lastFileContent = content; + this.app.workspace.on( + "editor-change", + debounce(async (editor, view) => { + if (view instanceof MarkdownView && this.sidebarView) { + // Lấy nội dung hiện tại của editor + const content = editor.getValue(); + + // Kiểm tra nếu nội dung thay đổi và có chứa task hoặc nội dung cũ có chứa task + if ( + content.includes("- [") || + content.includes("- [ ]") || + content.includes("- [x]") || + this.lastFileContent.includes("- [") || + this.lastFileContent.includes("- [ ]") || + this.lastFileContent.includes("- [x]") + ) { + // Cập nhật ngay lập tức + if (this.lastActiveFile) { + // Cập nhật nội dung file cuối cùng trước khi kiểm tra thay đổi + this.lastActiveFile = view.file; + + // Cập nhật thanh tiến trình ngay lập tức + this.sidebarView.updateProgressBar( + view.file, + content + ); + + // Sau đó mới cập nhật nội dung file cuối cùng + this.lastFileContent = content; + } } } - } - }, this.settings.editorChangeDelay)) // Use configurable delay + }, this.settings.editorChangeDelay) + ) // Use configurable delay ); // Lắng nghe sự kiện keydown để phát hiện khi người dùng nhập task mới hoặc check/uncheck task - this.registerDomEvent(document, 'keydown', (evt: KeyboardEvent) => { + this.registerDomEvent(document, "keydown", (evt: KeyboardEvent) => { // Kiểm tra xem có đang trong editor không - const activeView = this.app.workspace.getActiveViewOfType(MarkdownView); - if (activeView && activeView.getMode() === 'source') { + const activeView = + this.app.workspace.getActiveViewOfType(MarkdownView); + if (activeView && activeView.getMode() === "source") { // Cập nhật ngay lập tức khi nhấn các phím liên quan đến task - if (['Enter', 'Space', ']', 'x', 'X', 'Backspace', 'Delete'].includes(evt.key)) { + if ( + [ + "Enter", + "Space", + "]", + "x", + "X", + "Backspace", + "Delete", + ].includes(evt.key) + ) { // Cập nhật ngay lập tức setTimeout(() => { const content = activeView.editor.getValue(); - if (content.includes('- [') || content.includes('- [ ]') || content.includes('- [x]')) { + if ( + content.includes("- [") || + content.includes("- [ ]") || + content.includes("- [x]") + ) { this.lastActiveFile = activeView.file; - + // Cập nhật thanh tiến trình ngay lập tức if (this.sidebarView) { - this.sidebarView.updateProgressBar(activeView.file, content); + this.sidebarView.updateProgressBar( + activeView.file, + content + ); } - + // Sau đó mới cập nhật nội dung file cuối cùng this.lastFileContent = content; } - }, this.settings.keyboardInputDelay); // Use configurable delay + }, this.settings.keyboardInputDelay); // Use configurable delay } } }); // Lắng nghe sự kiện click trong editor để phát hiện khi task được check/uncheck - this.registerDomEvent(document, 'click', (evt: MouseEvent) => { + this.registerDomEvent(document, "click", (evt: MouseEvent) => { const target = evt.target as HTMLElement; - + // Kiểm tra xem click có phải vào checkbox của task không - if (target && target.tagName === 'INPUT' && target.classList.contains('task-list-item-checkbox')) { + if ( + target && + target.tagName === "INPUT" && + target.classList.contains("task-list-item-checkbox") + ) { // Đợi một chút để Obsidian cập nhật trạng thái task trong file setTimeout(async () => { const activeFile = this.app.workspace.getActiveFile(); if (activeFile && this.sidebarView) { // Đọc nội dung file hiện tại const content = await this.app.vault.read(activeFile); - + // Cập nhật thanh tiến trình ngay lập tức this.lastActiveFile = activeFile; this.sidebarView.updateProgressBar(activeFile, content); - + // Sau đó mới cập nhật nội dung file cuối cùng this.lastFileContent = content; - } - }, this.settings.checkboxClickDelay); // Use configurable delay + } + }, this.settings.checkboxClickDelay); // Use configurable delay } }); // Activate view when plugin loads - đợi một chút để Obsidian khởi động hoàn toàn setTimeout(() => { this.activateView(); + + // We'll use a single delayed update instead of multiple updates + setTimeout(async () => { + const currentFile = this.app.workspace.getActiveFile(); + if (currentFile && this.sidebarView) { + if (this.settings.showDebugInfo) { + console.log( + "Initial file load after plugin start:", + currentFile.path + ); + } + + await this.updateLastFileContent(currentFile); + // Use a flag to indicate this is the initial load + this.sidebarView.updateProgressBar( + currentFile, + undefined, + true + ); + } + }, 1500); }, 1000); } @@ -204,19 +292,19 @@ export default class TaskProgressBarPlugin extends Plugin { checkDataviewAPI() { // Kiểm tra ngay lập tức this.dvAPI = getDataviewAPI(this.app); - + // Nếu không tìm thấy, thiết lập interval để kiểm tra lại if (!this.dvAPI) { this.dataviewCheckInterval = window.setInterval(() => { this.dvAPI = getDataviewAPI(this.app); if (this.dvAPI) { - console.log('Dataview API found'); + console.log("Dataview API found"); // Nếu tìm thấy, xóa interval if (this.dataviewCheckInterval) { clearInterval(this.dataviewCheckInterval); this.dataviewCheckInterval = null; } - + // Cập nhật sidebar nếu đang mở if (this.sidebarView && this.lastActiveFile) { this.sidebarView.updateProgressBar(this.lastActiveFile); @@ -230,22 +318,29 @@ export default class TaskProgressBarPlugin extends Plugin { hasTaskStatusChanged(newContent: string): boolean { // Nếu chưa có nội dung cũ, coi như đã thay đổi if (!this.lastFileContent) return true; - + // Đếm số lượng task hoàn thành trong nội dung cũ và mới // Sử dụng regex chính xác hơn để phát hiện task - const oldCompletedTasks = (this.lastFileContent.match(/- \[x\]/gi) || []).length; + const oldCompletedTasks = ( + this.lastFileContent.match(/- \[x\]/gi) || [] + ).length; const newCompletedTasks = (newContent.match(/- \[x\]/gi) || []).length; - + // Đếm tổng số task trong nội dung cũ và mới // Sử dụng regex chính xác hơn để phát hiện task chưa hoàn thành - const oldIncompleteTasks = (this.lastFileContent.match(/- \[ \]/g) || []).length; + const oldIncompleteTasks = ( + this.lastFileContent.match(/- \[ \]/g) || [] + ).length; const newIncompleteTasks = (newContent.match(/- \[ \]/g) || []).length; - + const oldTotalTasks = oldIncompleteTasks + oldCompletedTasks; const newTotalTasks = newIncompleteTasks + newCompletedTasks; - + // Nếu số lượng task hoàn thành hoặc tổng số task thay đổi, coi như đã thay đổi - return oldCompletedTasks !== newCompletedTasks || oldTotalTasks !== newTotalTasks; + return ( + oldCompletedTasks !== newCompletedTasks || + oldTotalTasks !== newTotalTasks + ); } // Cập nhật nội dung file cuối cùng @@ -258,9 +353,9 @@ export default class TaskProgressBarPlugin extends Plugin { async activateView() { try { const { workspace } = this.app; - + // If view already exists in leaves, show it - const leaves = workspace.getLeavesOfType('progress-tracker'); + const leaves = workspace.getLeavesOfType("progress-tracker"); if (leaves.length > 0) { workspace.revealLeaf(leaves[0]); return; @@ -269,25 +364,27 @@ export default class TaskProgressBarPlugin extends Plugin { // Otherwise, create a new leaf in the left sidebar // Kiểm tra xem workspace đã sẵn sàng chưa if (!workspace.leftSplit) { - console.log('Workspace not ready yet, retrying in 500ms'); + console.log("Workspace not ready yet, retrying in 500ms"); setTimeout(() => this.activateView(), 500); return; } - + // Sử dụng getLeaf thay vì createLeaf const leaf = workspace.getLeftLeaf(false); if (leaf) { await leaf.setViewState({ - type: 'progress-tracker', + type: "progress-tracker", active: true, }); - + // Reveal the leaf workspace.revealLeaf(leaf); } } catch (error) { - console.error('Error activating view:', error); - new Notice('Error activating Task Progress Bar view. Please try again later.'); + console.error("Error activating view:", error); + new Notice( + "Error activating Task Progress Bar view. Please try again later." + ); } } @@ -297,17 +394,61 @@ export default class TaskProgressBarPlugin extends Plugin { clearInterval(this.dataviewCheckInterval); this.dataviewCheckInterval = null; } - + // Detach the view when plugin unloads - this.app.workspace.detachLeavesOfType('progress-tracker'); + this.app.workspace.detachLeavesOfType("progress-tracker"); } async loadSettings() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + this.settings = Object.assign( + {}, + DEFAULT_SETTINGS, + await this.loadData() + ); } async saveSettings() { - await this.saveData(this.settings); + try { + await this.saveData(this.settings); + // Apply the max-height style whenever settings are saved + this.applyMaxTabsHeightStyle(); + } catch (error) { + console.error("Error saving settings:", error); + new Notice("Error saving settings. See console for details."); + } + } + + // Improved method to apply the max-height style + applyMaxTabsHeightStyle() { + try { + // Remove any existing style element first + const existingStyle = document.getElementById('progress-tracker-max-tabs-height'); + if (existingStyle) { + existingStyle.remove(); + } + + // Create a new style element + const style = document.createElement('style'); + style.id = 'progress-tracker-max-tabs-height'; + + // Set the CSS rule with the user's preference + style.textContent = ` + .workspace-tabs.mod-top.mod-top-right-space:not(.mod-top-left-space) { + max-height: ${this.settings.maxTabsHeight} !important; + transition: max-height 0.3s ease; + } + `; + + // Add the style to the document head + document.head.appendChild(style); + + // Debug info + if (this.settings.showDebugInfo) { + console.log(`Applied max-tabs-height: ${this.settings.maxTabsHeight}`); + } + } catch (error) { + console.error("Error applying max tabs height style:", error); + } } } @@ -316,6 +457,8 @@ class TaskProgressBarView extends ItemView { currentFile: TFile | null = null; isVisible: boolean = false; lastUpdateTime: number = 0; + lastFileUpdateMap: Map = new Map(); // Track last update time per file + initialLoadComplete: boolean = false; // Track if initial load is complete constructor(leaf: WorkspaceLeaf, plugin: TaskProgressBarPlugin) { super(leaf); @@ -323,98 +466,135 @@ class TaskProgressBarView extends ItemView { } getViewType(): string { - return 'progress-tracker'; + return "progress-tracker"; } getDisplayText(): string { - return 'Task Progress Bar'; + return "Task Progress Bar"; } getIcon(): string { - return 'bar-chart-horizontal'; + return "bar-chart-horizontal"; } async onOpen() { this.isVisible = true; + this.initialLoadComplete = false; const container = this.containerEl.children[1]; container.empty(); - - // Create progress container (removed the h4 title) - const progressContainer = container.createDiv({ cls: 'task-progress-container' }); - - // Update with current file if available - const currentFile = this.app.workspace.getActiveFile(); - if (currentFile) { - this.updateProgressBar(currentFile); - } else { - // Hiển thị thông báo nếu không có file nào đang mở - progressContainer.createEl('p', { - text: 'No file is currently open. Open a markdown file to see the progress bar.' - }); - } + + // Create progress container + const progressContainer = container.createDiv({ + cls: "task-progress-container", + }); + + // Add loading indicator that we'll keep until the first real data loads + progressContainer.createEl("p", { + text: "Loading task progress...", + cls: "loading-indicator", + }); + + // For initial load, we'll wait for the main plugin to trigger the update + // rather than trying to load here, to avoid duplicate loading issues } - async updateProgressBar(file: TFile | null, content?: string) { + async updateProgressBar( + file: TFile | null, + content?: string, + isInitialLoad: boolean = false + ) { if (!file) return; - + + // Track the update time for this file + this.lastFileUpdateMap.set(file.path, Date.now()); + // Tránh cập nhật quá nhanh const now = Date.now(); - if (now - this.lastUpdateTime < 100) return; // Giảm thời gian chờ xuống 100ms + if (!isInitialLoad && now - this.lastUpdateTime < 100) return; this.lastUpdateTime = now; - + this.currentFile = file; - + const container = this.containerEl.children[1]; - const progressContainer = container.querySelector('.task-progress-container') as HTMLElement; + const progressContainer = container.querySelector( + ".task-progress-container" + ) as HTMLElement; if (!progressContainer) return; - + + // Clear the container once at the start if this is initial load + // This prevents showing the loading indicator and then progress bar + if (isInitialLoad || !this.initialLoadComplete) { + progressContainer.empty(); + this.initialLoadComplete = true; + } + // Thêm class để hiển thị animation chỉ khi cài đặt showUpdateAnimation được bật if (this.plugin.settings.showUpdateAnimation) { - progressContainer.classList.add('updating'); + progressContainer.classList.add("updating"); } - - // Cập nhật ngay lập tức nếu có nội dung được cung cấp - if (content) { - this.updateProgressBarContentWithString(content, progressContainer, file); - + + try { + // Cập nhật ngay lập tức nếu có nội dung được cung cấp + if (content) { + this.updateProgressBarContentWithString( + content, + progressContainer, + file + ); + } else { + // Đọc nội dung file + const fileContent = await this.plugin.app.vault.read(file); + + // Do a quick check for tasks to avoid resetting the progress bar unnecessarily + if (this.hasTasksInContent(fileContent)) { + this.updateProgressBarContentWithString( + fileContent, + progressContainer, + file + ); + } else if (this.plugin.settings.showDebugInfo) { + console.log("No tasks found in file:", file.path); + } + } + } catch (error) { + console.error("Error updating progress bar:", error); + } finally { // Xóa class sau khi cập nhật xong, chỉ khi animation được bật if (this.plugin.settings.showUpdateAnimation) { setTimeout(() => { - progressContainer.classList.remove('updating'); + progressContainer.classList.remove("updating"); }, this.plugin.settings.updateAnimationDelay); // Use configurable delay } - } else { - // Nếu không có nội dung, đọc từ file - setTimeout(async () => { - // Đọc nội dung file - const fileContent = await this.plugin.app.vault.read(file); - // Cập nhật với nội dung đọc được - this.updateProgressBarContentWithString(fileContent, progressContainer, file); - - // Xóa class sau khi cập nhật xong, chỉ khi animation được bật - if (this.plugin.settings.showUpdateAnimation) { - setTimeout(() => { - progressContainer.classList.remove('updating'); - }, this.plugin.settings.updateAnimationDelay); // Use configurable delay - } - }, 50); } } - + + // Helper method to quickly check if content has tasks + hasTasksInContent(content: string): boolean { + return /- \[[x ]\]/i.test(content) || /[-*] \[[x ]\]/i.test(content); + } + // Phương thức mới để cập nhật với nội dung string trực tiếp - async updateProgressBarContentWithString(content: string, progressContainer: HTMLElement, file: TFile) { - progressContainer.empty(); - + async updateProgressBarContentWithString( + content: string, + progressContainer: HTMLElement, + file: TFile + ) { + // Don't empty the container to prevent flashing - we'll replace content as needed + // Get Dataview API const dvAPI = this.plugin.dvAPI; if (!dvAPI) { - // Thêm thông báo nhỏ gọn về Dataview không khả dụng - const dataviewWarning = progressContainer.createDiv({ cls: 'dataview-warning-compact' }); - dataviewWarning.createEl('span', { - text: 'Dataview not available', - cls: 'dataview-warning-text' - }); - + // Only update warning if it doesn't already exist + if (!progressContainer.querySelector(".dataview-warning-compact")) { + progressContainer.empty(); + const dataviewWarning = progressContainer.createDiv({ + cls: "dataview-warning-compact", + }); + dataviewWarning.createEl("span", { + text: "Dataview not available", + cls: "dataview-warning-text", + }); + } return; } @@ -422,103 +602,210 @@ class TaskProgressBarView extends ItemView { // Tạo thanh tiến trình trực tiếp từ nội dung this.createProgressBarFromString(progressContainer, content, file); } catch (error) { - console.error('Error updating progress bar:', error); - progressContainer.createEl('p', { text: 'Error loading progress bar' }); + console.error("Error updating progress bar:", error); + progressContainer.empty(); + progressContainer.createEl("p", { + text: "Error loading progress bar", + }); } } - + // Phương thức tạo thanh tiến trình từ nội dung string - async createProgressBarFromString(container: HTMLElement, content: string, file: TFile) { + async createProgressBarFromString( + container: HTMLElement, + content: string, + file: TFile + ) { try { + // Log for debugging if enabled + if (this.plugin.settings.showDebugInfo) { + console.log(`Creating progress bar for file: ${file.path}`); + console.log(`Content length: ${content.length}`); + } + // Sử dụng regex chính xác hơn để đếm task + // Use a more flexible regex that can catch tasks in various formats const incompleteTasks = (content.match(/- \[ \]/g) || []).length; const completedTasks = (content.match(/- \[x\]/gi) || []).length; // Thêm cờ 'i' để bắt cả 'x' và 'X' - const totalTasks = incompleteTasks + completedTasks; - + let totalTasks = incompleteTasks + completedTasks; + + // Try with relaxed regex if needed + let relaxedIncompleteTasks = 0; + let relaxedCompletedTasks = 0; if (totalTasks === 0) { - container.createEl('p', { text: 'No tasks found in this file' }); + relaxedIncompleteTasks = (content.match(/[-*] \[ \]/g) || []) + .length; + relaxedCompletedTasks = (content.match(/[-*] \[x\]/gi) || []) + .length; + totalTasks = relaxedIncompleteTasks + relaxedCompletedTasks; + } + + // Log task counts for debugging + if (this.plugin.settings.showDebugInfo) { + console.log( + `Task counts - incomplete: ${incompleteTasks}, completed: ${completedTasks}, total: ${totalTasks}` + ); + if (relaxedIncompleteTasks > 0 || relaxedCompletedTasks > 0) { + console.log( + `Using relaxed regex - found tasks: ${ + relaxedIncompleteTasks + relaxedCompletedTasks + }` + ); + } + } + + if (totalTasks === 0) { + // Only update the container if it's not already showing "No tasks found" + const existingNoTasks = container.querySelector("p"); + if ( + !existingNoTasks || + existingNoTasks.textContent !== + "No tasks found in this file" + ) { + container.empty(); + container.createEl("p", { + text: "No tasks found in this file", + }); + } return; } - - const percentage = Math.round((completedTasks / totalTasks) * 100); - - // Create a more compact layout - const progressLayout = container.createDiv({ cls: 'progress-layout' }); - - // Create percentage text with smaller size - progressLayout.createEl('div', { - text: `${percentage}%`, - cls: 'progress-percentage-small' - }); - - // Create HTML5-like progress bar - const progressBarContainer = progressLayout.createDiv({ cls: 'pt-progress-bar-container' }); - - // Create the outer progress element (similar to HTML ) - const progressElement = progressBarContainer.createDiv({ cls: 'progress-element' }); - - // Create the inner value element that shows the filled portion - const progressValue = progressElement.createDiv({ cls: 'progress-value' }); - progressValue.style.width = `${percentage}%`; - - // Apply color based on settings - this.applyProgressColor(progressValue, percentage); - - // Add a data attribute for potential CSS styling based on percentage - progressElement.setAttribute('data-percentage', percentage.toString()); - - // Create stats (keep this but make it more compact) - const statsContainer = container.createDiv({ cls: 'progress-stats-compact' }); - statsContainer.createSpan({ text: `${completedTasks}/${totalTasks} tasks` }); - - // Thêm debug info nếu cần - if (this.plugin.settings.showDebugInfo) { - const debugInfo = container.createDiv({ cls: 'debug-info' }); - debugInfo.createEl('p', { text: `Debug Info:` }); - debugInfo.createEl('p', { text: `File: ${file.path}` }); - debugInfo.createEl('p', { text: `Incomplete tasks: ${incompleteTasks}` }); - debugInfo.createEl('p', { text: `Completed tasks: ${completedTasks}` }); - debugInfo.createEl('p', { text: `Total tasks: ${totalTasks}` }); - debugInfo.createEl('p', { text: `Percentage: ${percentage}%` }); - debugInfo.createEl('p', { text: `Update time: ${new Date().toISOString()}` }); - debugInfo.createEl('p', { text: `Color scheme: ${this.plugin.settings.progressColorScheme}` }); - } + + // We found tasks, so clear and update the container + container.empty(); + + // Calculate percentage based on which regex found tasks + let completedCount = + incompleteTasks > 0 || completedTasks > 0 + ? completedTasks + : relaxedCompletedTasks; + + const percentage = Math.round((completedCount / totalTasks) * 100); + + // Create the progress bar elements + this.createProgressBarElements( + container, + completedCount, + totalTasks, + percentage + ); } catch (error) { - console.error('Error creating progress bar from string:', error); - container.createEl('p', { text: 'Error creating progress bar' }); + console.error("Error creating progress bar from string:", error); + // Only update if container isn't already showing an error + const errorParagraph = container.querySelector("p"); + if ( + !errorParagraph || + !errorParagraph.textContent?.includes("Error") + ) { + container.empty(); + container.createEl("p", { + text: "Error creating progress bar", + }); + } + } + } + + // New helper method to create progress bar elements + createProgressBarElements( + container: HTMLElement, + completedTasks: number, + totalTasks: number, + percentage: number + ) { + // Create a more compact layout + const progressLayout = container.createDiv({ cls: "progress-layout" }); + + // Create percentage text with smaller size + progressLayout.createEl("div", { + text: `${percentage}%`, + cls: "progress-percentage-small", + }); + + // Create HTML5-like progress bar + const progressBarContainer = progressLayout.createDiv({ + cls: "pt-progress-bar-container", + }); + + // Create the outer progress element (similar to HTML ) + const progressElement = progressBarContainer.createDiv({ + cls: "progress-element", + }); + + // Create the inner value element that shows the filled portion + const progressValue = progressElement.createDiv({ + cls: "progress-value", + }); + progressValue.style.width = `${percentage}%`; + + // Apply color based on settings + this.applyProgressColor(progressValue, percentage); + + // Add a data attribute for potential CSS styling based on percentage + progressElement.setAttribute("data-percentage", percentage.toString()); + + // Create stats (keep this but make it more compact) + const statsContainer = container.createDiv({ + cls: "progress-stats-compact", + }); + statsContainer.createSpan({ + text: `${completedTasks}/${totalTasks} tasks`, + }); + + // Thêm debug info nếu cần + if (this.plugin.settings.showDebugInfo) { + const debugInfo = container.createDiv({ cls: "debug-info" }); + debugInfo.createEl("p", { text: `Debug Info:` }); + debugInfo.createEl("p", { + text: `File: ${this.currentFile?.path}`, + }); + debugInfo.createEl("p", { + text: `Incomplete tasks: ${totalTasks - completedTasks}`, + }); + debugInfo.createEl("p", { + text: `Completed tasks: ${completedTasks}`, + }); + debugInfo.createEl("p", { text: `Total tasks: ${totalTasks}` }); + debugInfo.createEl("p", { text: `Percentage: ${percentage}%` }); + debugInfo.createEl("p", { + text: `Update time: ${new Date().toISOString()}`, + }); + debugInfo.createEl("p", { + text: `Color scheme: ${this.plugin.settings.progressColorScheme}`, + }); } } async onClose() { this.isVisible = false; } - + // Method to apply color based on percentage and settings applyProgressColor(progressElement: HTMLElement, percentage: number) { const settings = this.plugin.settings; - + // If using default color scheme, let CSS handle it - if (settings.progressColorScheme === 'default') { + if (settings.progressColorScheme === "default") { // Xóa bất kỳ màu inline nào đã được đặt trước đó - progressElement.style.backgroundColor = ''; + progressElement.style.backgroundColor = ""; return; } - + // Apply custom color based on percentage if (percentage === 100) { // Hoàn thành - màu xanh lá - progressElement.style.backgroundColor = settings.completeProgressColor; + progressElement.style.backgroundColor = + settings.completeProgressColor; } else if (percentage >= settings.mediumProgressThreshold) { // Tiến độ cao (66-99%) - màu xanh dương progressElement.style.backgroundColor = settings.highProgressColor; } else if (percentage >= settings.lowProgressThreshold) { // Tiến độ trung bình (34-65%) - màu cam/vàng - progressElement.style.backgroundColor = settings.mediumProgressColor; + progressElement.style.backgroundColor = + settings.mediumProgressColor; } else { // Tiến độ thấp (0-33%) - màu đỏ progressElement.style.backgroundColor = settings.lowProgressColor; } - + // Thêm debug log nếu cần if (this.plugin.settings.showDebugInfo) { console.log(`Applied color for ${percentage}%: @@ -544,347 +831,499 @@ class TaskProgressBarSettingTab extends PluginSettingTab { containerEl.empty(); - containerEl.createEl('h2', { text: 'Task Progress Bar Settings' }); - + containerEl.createEl("h2", { text: "Task Progress Bar Settings" }); + // Thêm thông tin về trạng thái Dataview - const dataviewStatus = containerEl.createDiv({ cls: 'dataview-status' }); + const dataviewStatus = containerEl.createDiv({ + cls: "dataview-status", + }); if (this.plugin.dvAPI) { - dataviewStatus.createEl('p', { - text: '✅ Dataview API is available', - cls: 'dataview-available' + dataviewStatus.createEl("p", { + text: "✅ Dataview API is available", + cls: "dataview-available", }); } else { - dataviewStatus.createEl('p', { - text: '❌ Dataview API is not available', - cls: 'dataview-unavailable' + dataviewStatus.createEl("p", { + text: "❌ Dataview API is not available", + cls: "dataview-unavailable", }); - + // Thêm nút để kiểm tra lại Dataview - const checkButton = dataviewStatus.createEl('button', { - text: 'Check for Dataview', - cls: 'mod-cta' + const checkButton = dataviewStatus.createEl("button", { + text: "Check for Dataview", + cls: "mod-cta", }); - checkButton.addEventListener('click', () => { + checkButton.addEventListener("click", () => { this.plugin.checkDataviewAPI(); if (this.plugin.dvAPI) { - new Notice('Dataview API found!'); + new Notice("Dataview API found!"); this.display(); // Refresh settings tab } else { - new Notice('Dataview API not found. Make sure Dataview plugin is installed and enabled.'); + new Notice( + "Dataview API not found. Make sure Dataview plugin is installed and enabled." + ); } }); } // General settings section - containerEl.createEl('h3', { text: 'General Settings' }); + containerEl.createEl("h3", { text: "General Settings" }); new Setting(containerEl) - .setName('Setting') - .setDesc('Description of the setting') - .addText(text => text - .setPlaceholder('Enter your setting') - .setValue(this.plugin.settings.mySetting) - .onChange(async (value) => { - this.plugin.settings.mySetting = value; - await this.plugin.saveSettings(); - })); - + .setName("Setting") + .setDesc("Description of the setting") + .addText((text) => + text + .setPlaceholder("Enter your setting") + .setValue(this.plugin.settings.mySetting) + .onChange(async (value) => { + this.plugin.settings.mySetting = value; + await this.plugin.saveSettings(); + }) + ); + new Setting(containerEl) - .setName('Show Debug Info') - .setDesc('Show debug information in the sidebar to help troubleshoot task counting issues') - .addToggle(toggle => toggle - .setValue(this.plugin.settings.showDebugInfo) - .onChange(async (value) => { - this.plugin.settings.showDebugInfo = value; - await this.plugin.saveSettings(); - - // Cập nhật sidebar nếu đang mở - sử dụng phương thức public - const currentFile = this.app.workspace.getActiveFile(); - if (currentFile) { - // Sử dụng phương thức public để cập nhật UI - this.plugin.checkDataviewAPI(); - } - })); + .setName("Show Debug Info") + .setDesc( + "Show debug information in the sidebar to help troubleshoot task counting issues" + ) + .addToggle((toggle) => + toggle + .setValue(this.plugin.settings.showDebugInfo) + .onChange(async (value) => { + this.plugin.settings.showDebugInfo = value; + await this.plugin.saveSettings(); + + // Cập nhật sidebar nếu đang mở - sử dụng phương thức public + const currentFile = this.app.workspace.getActiveFile(); + if (currentFile) { + // Sử dụng phương thức public để cập nhật UI + this.plugin.checkDataviewAPI(); + } + }) + ); // Animation settings section - containerEl.createEl('h3', { text: 'Animation Settings' }); - + containerEl.createEl("h3", { text: "Animation Settings" }); + // Add new setting for animation new Setting(containerEl) - .setName('Show Update Animation') - .setDesc('Show a brief animation when updating the progress bar') - .addToggle(toggle => toggle - .setValue(this.plugin.settings.showUpdateAnimation) - .onChange(async (value) => { - this.plugin.settings.showUpdateAnimation = value; - await this.plugin.saveSettings(); - - // No need to update the view here as it will only affect future updates - })); - - new Setting(containerEl) - .setName('Update Animation Delay') - .setDesc('Duration of the update animation in milliseconds (higher = longer animation)') - .addSlider(slider => slider - .setLimits(100, 1000, 50) // From 100ms to 1000ms in steps of 50ms - .setValue(this.plugin.settings.updateAnimationDelay) - .setDynamicTooltip() - .onChange(async (value) => { - this.plugin.settings.updateAnimationDelay = value; - await this.plugin.saveSettings(); - // No need to update the view as this affects future animations - })) - .addExtraButton(button => button - .setIcon('reset') - .setTooltip('Reset to default (300ms)') - .onClick(async () => { - this.plugin.settings.updateAnimationDelay = 300; - await this.plugin.saveSettings(); - this.display(); // Refresh the settings panel to update the slider - })); + .setName("Show Update Animation") + .setDesc("Show a brief animation when updating the progress bar") + .addToggle((toggle) => + toggle + .setValue(this.plugin.settings.showUpdateAnimation) + .onChange(async (value) => { + this.plugin.settings.showUpdateAnimation = value; + await this.plugin.saveSettings(); + + // No need to update the view here as it will only affect future updates + }) + ); // Performance settings section - containerEl.createEl('h3', { text: 'Performance Settings' }); - + containerEl.createEl("h3", { text: "Performance Settings" }); + new Setting(containerEl) - .setName('Editor Change Delay') - .setDesc('Delay before updating after editor content changes (lower = more responsive, higher = better performance)') - .addSlider(slider => slider - .setLimits(100, 1000, 50) // From 100ms to 1000ms in steps of 50ms - .setValue(this.plugin.settings.editorChangeDelay) - .setDynamicTooltip() - .onChange(async (value) => { - this.plugin.settings.editorChangeDelay = value; - await this.plugin.saveSettings(); - // Note: This will take effect after plugin reload - new Notice("Editor change delay updated. Restart plugin to apply changes."); - })) - .addExtraButton(button => button - .setIcon('reset') - .setTooltip('Reset to default (500ms)') - .onClick(async () => { - this.plugin.settings.editorChangeDelay = 500; - await this.plugin.saveSettings(); - this.display(); // Refresh the settings panel to update the slider - new Notice("Editor change delay reset. Restart plugin to apply changes."); - })); - + .setName("Editor Change Delay") + .setDesc( + "Delay before updating after editor content changes (lower = more responsive, higher = better performance)" + ) + .addSlider((slider) => + slider + .setLimits(100, 1000, 50) // From 100ms to 1000ms in steps of 50ms + .setValue(this.plugin.settings.editorChangeDelay) + .setDynamicTooltip() + .onChange(async (value) => { + this.plugin.settings.editorChangeDelay = value; + await this.plugin.saveSettings(); + // Note: This will take effect after plugin reload + new Notice( + "Editor change delay updated. Restart plugin to apply changes." + ); + }) + ) + .addExtraButton((button) => + button + .setIcon("reset") + .setTooltip("Reset to default (500ms)") + .onClick(async () => { + this.plugin.settings.editorChangeDelay = 500; + await this.plugin.saveSettings(); + this.display(); // Refresh the settings panel to update the slider + new Notice( + "Editor change delay reset. Restart plugin to apply changes." + ); + }) + ); + new Setting(containerEl) - .setName('Keyboard Input Delay') - .setDesc('Delay after keyboard input before updating progress (in milliseconds)') - .addSlider(slider => slider - .setLimits(100, 1000, 50) // From 0ms to 100ms in steps of 5ms - .setValue(this.plugin.settings.keyboardInputDelay) - .setDynamicTooltip() - .onChange(async (value) => { - this.plugin.settings.keyboardInputDelay = value; - await this.plugin.saveSettings(); - })) - .addExtraButton(button => button - .setIcon('reset') - .setTooltip('Reset to default (100ms)') - .onClick(async () => { - this.plugin.settings.keyboardInputDelay = 100; - await this.plugin.saveSettings(); - this.display(); // Refresh the settings panel to update the slider - })); - + .setName("Keyboard Input Delay") + .setDesc( + "Delay after keyboard input before updating progress (in milliseconds)" + ) + .addSlider((slider) => + slider + .setLimits(100, 1000, 50) // From 0ms to 100ms in steps of 5ms + .setValue(this.plugin.settings.keyboardInputDelay) + .setDynamicTooltip() + .onChange(async (value) => { + this.plugin.settings.keyboardInputDelay = value; + await this.plugin.saveSettings(); + }) + ) + .addExtraButton((button) => + button + .setIcon("reset") + .setTooltip("Reset to default (100ms)") + .onClick(async () => { + this.plugin.settings.keyboardInputDelay = 100; + await this.plugin.saveSettings(); + this.display(); // Refresh the settings panel to update the slider + }) + ); + new Setting(containerEl) - .setName('Checkbox Click Delay') - .setDesc('Delay after checkbox click before updating progress (in milliseconds)') - .addSlider(slider => slider - .setLimits(100, 1000, 50) // From 10ms to 200ms in steps of 10ms - .setValue(this.plugin.settings.checkboxClickDelay) - .setDynamicTooltip() - .onChange(async (value) => { - this.plugin.settings.checkboxClickDelay = value; - await this.plugin.saveSettings(); - })) - .addExtraButton(button => button - .setIcon('reset') - .setTooltip('Reset to default (200ms)') - .onClick(async () => { - this.plugin.settings.checkboxClickDelay = 200; - await this.plugin.saveSettings(); - this.display(); // Refresh the settings panel to update the slider - })); - + .setName("Checkbox Click Delay") + .setDesc( + "Delay after checkbox click before updating progress (in milliseconds)" + ) + .addSlider((slider) => + slider + .setLimits(100, 1000, 50) // From 10ms to 200ms in steps of 10ms + .setValue(this.plugin.settings.checkboxClickDelay) + .setDynamicTooltip() + .onChange(async (value) => { + this.plugin.settings.checkboxClickDelay = value; + await this.plugin.saveSettings(); + }) + ) + .addExtraButton((button) => + button + .setIcon("reset") + .setTooltip("Reset to default (200ms)") + .onClick(async () => { + this.plugin.settings.checkboxClickDelay = 200; + await this.plugin.saveSettings(); + this.display(); // Refresh the settings panel to update the slider + }) + ); + // Add color scheme settings - containerEl.createEl('h3', { text: 'Progress Bar Colors' }); - + containerEl.createEl("h3", { text: "Progress Bar Colors" }); + new Setting(containerEl) - .setName('Color Scheme') - .setDesc('Choose a color scheme for the progress bar') - .addDropdown(dropdown => dropdown - .addOption('default', 'Default (Theme Colors)') - .addOption('red-orange-green', 'Red-Orange-Blue-Green') - .addOption('custom', 'Custom Colors') - .setValue(this.plugin.settings.progressColorScheme) - .onChange(async (value: 'default' | 'red-orange-green' | 'custom') => { - this.plugin.settings.progressColorScheme = value; - - // Set preset colors if red-orange-green is selected - if (value === 'red-orange-green') { - // Đặt lại các giá trị màu sắc và ngưỡng - this.plugin.settings.lowProgressColor = '#e06c75'; // Red - this.plugin.settings.mediumProgressColor = '#e5c07b'; // Orange/Yellow - this.plugin.settings.highProgressColor = '#61afef'; // Blue - this.plugin.settings.completeProgressColor = '#98c379'; // Green - - // Đặt lại các ngưỡng - this.plugin.settings.lowProgressThreshold = 30; - this.plugin.settings.mediumProgressThreshold = 60; - this.plugin.settings.highProgressThreshold = 99; - - // Hiển thị thông báo để xác nhận thay đổi - new Notice('Applied Red-Orange-Blue-Green color scheme'); - } - - await this.plugin.saveSettings(); - this.display(); // Refresh to show/hide custom color options - - // Update the view if open - const currentFile = this.app.workspace.getActiveFile(); - if (currentFile && this.plugin.sidebarView) { - this.plugin.sidebarView.updateProgressBar(currentFile); - } - })); - + .setName("Color Scheme") + .setDesc("Choose a color scheme for the progress bar") + .addDropdown((dropdown) => + dropdown + .addOption("default", "Default (Theme Colors)") + .addOption("red-orange-green", "Red-Orange-Blue-Green") + .addOption("custom", "Custom Colors") + .setValue(this.plugin.settings.progressColorScheme) + .onChange( + async ( + value: "default" | "red-orange-green" | "custom" + ) => { + this.plugin.settings.progressColorScheme = value; + + // Set preset colors if red-orange-green is selected + if (value === "red-orange-green") { + // Đặt lại các giá trị màu sắc và ngưỡng + this.plugin.settings.lowProgressColor = + "#e06c75"; // Red + this.plugin.settings.mediumProgressColor = + "#e5c07b"; // Orange/Yellow + this.plugin.settings.highProgressColor = + "#61afef"; // Blue + this.plugin.settings.completeProgressColor = + "#98c379"; // Green + + // Đặt lại các ngưỡng + this.plugin.settings.lowProgressThreshold = 30; + this.plugin.settings.mediumProgressThreshold = 60; + this.plugin.settings.highProgressThreshold = 99; + + // Hiển thị thông báo để xác nhận thay đổi + new Notice( + "Applied Red-Orange-Blue-Green color scheme" + ); + } + + await this.plugin.saveSettings(); + this.display(); // Refresh to show/hide custom color options + + // Update the view if open + const currentFile = + this.app.workspace.getActiveFile(); + if (currentFile && this.plugin.sidebarView) { + this.plugin.sidebarView.updateProgressBar( + currentFile + ); + } + } + ) + ); + // Only show custom color settings if custom is selected - if (this.plugin.settings.progressColorScheme === 'custom') { + if (this.plugin.settings.progressColorScheme === "custom") { new Setting(containerEl) - .setName('Low Progress Color') - .setDesc(`Color for progress below ${this.plugin.settings.lowProgressThreshold}%`) - .addText(text => text - .setValue(this.plugin.settings.lowProgressColor) - .onChange(async (value) => { - this.plugin.settings.lowProgressColor = value; - await this.plugin.saveSettings(); - - // Update the view if open - const currentFile = this.app.workspace.getActiveFile(); - if (currentFile && this.plugin.sidebarView) { - this.plugin.sidebarView.updateProgressBar(currentFile); - } - })); - + .setName("Low Progress Color") + .setDesc( + `Color for progress below ${this.plugin.settings.lowProgressThreshold}%` + ) + .addText((text) => + text + .setValue(this.plugin.settings.lowProgressColor) + .onChange(async (value) => { + this.plugin.settings.lowProgressColor = value; + await this.plugin.saveSettings(); + + // Update the view if open + const currentFile = + this.app.workspace.getActiveFile(); + if (currentFile && this.plugin.sidebarView) { + this.plugin.sidebarView.updateProgressBar( + currentFile + ); + } + }) + ); + new Setting(containerEl) - .setName('Medium Progress Color') - .setDesc(`Color for progress between ${this.plugin.settings.lowProgressThreshold}% and ${this.plugin.settings.mediumProgressThreshold}%`) - .addText(text => text - .setValue(this.plugin.settings.mediumProgressColor) - .onChange(async (value) => { - this.plugin.settings.mediumProgressColor = value; - await this.plugin.saveSettings(); - - // Update the view if open - const currentFile = this.app.workspace.getActiveFile(); - if (currentFile && this.plugin.sidebarView) { - this.plugin.sidebarView.updateProgressBar(currentFile); - } - })); - + .setName("Medium Progress Color") + .setDesc( + `Color for progress between ${this.plugin.settings.lowProgressThreshold}% and ${this.plugin.settings.mediumProgressThreshold}%` + ) + .addText((text) => + text + .setValue(this.plugin.settings.mediumProgressColor) + .onChange(async (value) => { + this.plugin.settings.mediumProgressColor = value; + await this.plugin.saveSettings(); + + // Update the view if open + const currentFile = + this.app.workspace.getActiveFile(); + if (currentFile && this.plugin.sidebarView) { + this.plugin.sidebarView.updateProgressBar( + currentFile + ); + } + }) + ); + new Setting(containerEl) - .setName('High Progress Color') - .setDesc(`Color for progress between ${this.plugin.settings.mediumProgressThreshold}% and ${this.plugin.settings.highProgressThreshold}%`) - .addText(text => text - .setValue(this.plugin.settings.highProgressColor) - .onChange(async (value) => { - this.plugin.settings.highProgressColor = value; - await this.plugin.saveSettings(); - - // Update the view if open - const currentFile = this.app.workspace.getActiveFile(); - if (currentFile && this.plugin.sidebarView) { - this.plugin.sidebarView.updateProgressBar(currentFile); - } - })); - + .setName("High Progress Color") + .setDesc( + `Color for progress between ${this.plugin.settings.mediumProgressThreshold}% and ${this.plugin.settings.highProgressThreshold}%` + ) + .addText((text) => + text + .setValue(this.plugin.settings.highProgressColor) + .onChange(async (value) => { + this.plugin.settings.highProgressColor = value; + await this.plugin.saveSettings(); + + // Update the view if open + const currentFile = + this.app.workspace.getActiveFile(); + if (currentFile && this.plugin.sidebarView) { + this.plugin.sidebarView.updateProgressBar( + currentFile + ); + } + }) + ); + new Setting(containerEl) - .setName('Complete Progress Color') - .setDesc('Color for 100% progress') - .addText(text => text - .setValue(this.plugin.settings.completeProgressColor) - .onChange(async (value) => { - this.plugin.settings.completeProgressColor = value; - await this.plugin.saveSettings(); - - // Update the view if open - const currentFile = this.app.workspace.getActiveFile(); - if (currentFile && this.plugin.sidebarView) { - this.plugin.sidebarView.updateProgressBar(currentFile); - } - })); - + .setName("Complete Progress Color") + .setDesc("Color for 100% progress") + .addText((text) => + text + .setValue(this.plugin.settings.completeProgressColor) + .onChange(async (value) => { + this.plugin.settings.completeProgressColor = value; + await this.plugin.saveSettings(); + + // Update the view if open + const currentFile = + this.app.workspace.getActiveFile(); + if (currentFile && this.plugin.sidebarView) { + this.plugin.sidebarView.updateProgressBar( + currentFile + ); + } + }) + ); + // Add threshold settings new Setting(containerEl) - .setName('Low Progress Threshold') - .setDesc('Percentage below which progress is considered low') - .addSlider(slider => slider - .setLimits(1, 99, 1) - .setValue(this.plugin.settings.lowProgressThreshold) - .setDynamicTooltip() - .onChange(async (value) => { - // Ensure thresholds don't overlap - if (value >= this.plugin.settings.mediumProgressThreshold) { - value = this.plugin.settings.mediumProgressThreshold - 1; - } - this.plugin.settings.lowProgressThreshold = value; - await this.plugin.saveSettings(); - - // Update the view if open - const currentFile = this.app.workspace.getActiveFile(); - if (currentFile && this.plugin.sidebarView) { - this.plugin.sidebarView.updateProgressBar(currentFile); - } - })); - + .setName("Low Progress Threshold") + .setDesc("Percentage below which progress is considered low") + .addSlider((slider) => + slider + .setLimits(1, 99, 1) + .setValue(this.plugin.settings.lowProgressThreshold) + .setDynamicTooltip() + .onChange(async (value) => { + // Ensure thresholds don't overlap + if ( + value >= + this.plugin.settings.mediumProgressThreshold + ) { + value = + this.plugin.settings + .mediumProgressThreshold - 1; + } + this.plugin.settings.lowProgressThreshold = value; + await this.plugin.saveSettings(); + + // Update the view if open + const currentFile = + this.app.workspace.getActiveFile(); + if (currentFile && this.plugin.sidebarView) { + this.plugin.sidebarView.updateProgressBar( + currentFile + ); + } + }) + ); + new Setting(containerEl) - .setName('Medium Progress Threshold') - .setDesc('Percentage below which progress is considered medium') - .addSlider(slider => slider - .setLimits(1, 99, 1) - .setValue(this.plugin.settings.mediumProgressThreshold) - .setDynamicTooltip() - .onChange(async (value) => { - // Ensure thresholds don't overlap - if (value <= this.plugin.settings.lowProgressThreshold) { - value = this.plugin.settings.lowProgressThreshold + 1; - } - if (value >= this.plugin.settings.highProgressThreshold) { - value = this.plugin.settings.highProgressThreshold - 1; - } - this.plugin.settings.mediumProgressThreshold = value; - await this.plugin.saveSettings(); - - // Update the view if open - const currentFile = this.app.workspace.getActiveFile(); - if (currentFile && this.plugin.sidebarView) { - this.plugin.sidebarView.updateProgressBar(currentFile); - } - })); - + .setName("Medium Progress Threshold") + .setDesc("Percentage below which progress is considered medium") + .addSlider((slider) => + slider + .setLimits(1, 99, 1) + .setValue(this.plugin.settings.mediumProgressThreshold) + .setDynamicTooltip() + .onChange(async (value) => { + // Ensure thresholds don't overlap + if ( + value <= + this.plugin.settings.lowProgressThreshold + ) { + value = + this.plugin.settings.lowProgressThreshold + + 1; + } + if ( + value >= + this.plugin.settings.highProgressThreshold + ) { + value = + this.plugin.settings.highProgressThreshold - + 1; + } + this.plugin.settings.mediumProgressThreshold = + value; + await this.plugin.saveSettings(); + + // Update the view if open + const currentFile = + this.app.workspace.getActiveFile(); + if (currentFile && this.plugin.sidebarView) { + this.plugin.sidebarView.updateProgressBar( + currentFile + ); + } + }) + ); + new Setting(containerEl) - .setName('High Progress Threshold') - .setDesc('Percentage below which progress is considered high (but not complete)') - .addSlider(slider => slider - .setLimits(1, 99, 1) - .setValue(this.plugin.settings.highProgressThreshold) - .setDynamicTooltip() - .onChange(async (value) => { - // Ensure thresholds don't overlap - if (value <= this.plugin.settings.mediumProgressThreshold) { - value = this.plugin.settings.mediumProgressThreshold + 1; - } - this.plugin.settings.highProgressThreshold = value; - await this.plugin.saveSettings(); - - // Update the view if open - const currentFile = this.app.workspace.getActiveFile(); - if (currentFile && this.plugin.sidebarView) { - this.plugin.sidebarView.updateProgressBar(currentFile); - } - })); + .setName("High Progress Threshold") + .setDesc( + "Percentage below which progress is considered high (but not complete)" + ) + .addSlider((slider) => + slider + .setLimits(1, 99, 1) + .setValue(this.plugin.settings.highProgressThreshold) + .setDynamicTooltip() + .onChange(async (value) => { + // Ensure thresholds don't overlap + if ( + value <= + this.plugin.settings.mediumProgressThreshold + ) { + value = + this.plugin.settings + .mediumProgressThreshold + 1; + } + this.plugin.settings.highProgressThreshold = value; + await this.plugin.saveSettings(); + + // Update the view if open + const currentFile = + this.app.workspace.getActiveFile(); + if (currentFile && this.plugin.sidebarView) { + this.plugin.sidebarView.updateProgressBar( + currentFile + ); + } + }) + ); } + + // Interface settings section (Add this before or after Animation Settings) + containerEl.createEl("h3", { text: "Interface Settings" }); + + // Fix the Max Tabs Height setting to allow proper input + new Setting(containerEl) + .setName("Max Tabs Height") + .setDesc("Maximum height for workspace tabs (e.g., 110px, 200px, auto)") + .addText((text) => { + // Set initial value + text.setValue(this.plugin.settings.maxTabsHeight); + + // Apply improved validation only when the field loses focus + // This allows typing intermediary values that may not be valid yet + text.inputEl.addEventListener("blur", async () => { + const value = text.inputEl.value; + // Validate only when user is done editing + const isValid = + value === "auto" || + value === "none" || + /^\d+(\.\d+)?(px|em|rem|vh|%)$/.test(value); + + if (isValid) { + // Only update if changed to avoid unnecessary saves + if (this.plugin.settings.maxTabsHeight !== value) { + this.plugin.settings.maxTabsHeight = value; + await this.plugin.saveSettings(); + new Notice(`Max tabs height updated to ${value}`); + } + } else { + new Notice("Please enter 'auto', 'none' or a valid CSS length value (e.g., 110px)"); + // Reset to previous valid value + text.setValue(this.plugin.settings.maxTabsHeight); + } + }); + + // Add Enter key handling + text.inputEl.addEventListener("keydown", async (event) => { + if (event.key === "Enter") { + event.preventDefault(); + text.inputEl.blur(); // Trigger the blur event to validate + } + }); + + // Improve UX + text.inputEl.style.width = "120px"; + text.inputEl.placeholder = "auto"; + + return text; + }) + .addExtraButton(button => button + .setIcon("reset") + .setTooltip("Reset to default (auto)") + .onClick(async () => { + this.plugin.settings.maxTabsHeight = "auto"; + await this.plugin.saveSettings(); + this.display(); // Refresh the settings panel + new Notice("Max tabs height reset to 'auto'"); + }) + ); } } diff --git a/styles.css b/styles.css index 33e1315..a415b02 100644 --- a/styles.css +++ b/styles.css @@ -12,10 +12,9 @@ If your plugin does not need CSS, delete this file. */ /* Base container styles */ -.workspace-tabs.mod-top.mod-top-right-space:not(.mod-top-left-space) { +/* .workspace-tabs.mod-top.mod-top-right-space:not(.mod-top-left-space) { max-height: 110px !important; - height: 150px !important; -} +} */ .task-progress-container {