diff --git a/.gitignore b/.gitignore index 6cd3818b..0b2f8f20 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,10 @@ data.json # Exclude macOS Finder (System Explorer) View States .DS_Store +styles.css + +package-lock.json + # cursorrules .cursorrules @@ -33,4 +37,5 @@ data.json # translations scripts -translation-templates \ No newline at end of file +translation-templates +._data.json diff --git a/src/editor-ext/progressBarWidget.ts b/src/editor-ext/progressBarWidget.ts index 75d18f25..bfba8801 100644 --- a/src/editor-ext/progressBarWidget.ts +++ b/src/editor-ext/progressBarWidget.ts @@ -15,6 +15,7 @@ import { RegExpCursor } from "./regexp-cursor"; import TaskProgressBarPlugin, { showPopoverWithProgressBar } from ".."; import { shouldHideProgressBarInLivePriview } from "../utils"; import "../styles/progressbar.css"; +import { TaskGoalManager } from "src/utils/goal/main"; interface Tasks { completed: number; @@ -702,6 +703,10 @@ export function taskProgressBarExtension( line.from, line.to ); + + // Extract the task text and check for goal information + const customGoal = TaskGoalManager.extractTaskAndGoalInfo(lineText); + if ( !lineText || !/^[\s|\t]*([-*+]|\d+\.)\s\[(.)\]/.test(lineText) @@ -724,9 +729,10 @@ export function taskProgressBarExtension( if (!rangeText || rangeText.length === 1) continue; const tasksNum = this.extractTasksFromRange( - range, + range, view.state, - true + true, + customGoal // Add custom goal to the task count ); if (tasksNum.total === 0) continue; @@ -840,14 +846,15 @@ export function taskProgressBarExtension( private extractTasksFromRange( range: TextRange, state: EditorState, - isBullet: boolean + isBullet: boolean, + customGoal?: number | null ): Tasks { const textArray = this.getDocumentTextArray( state.doc, range.from, range.to ); - return this.calculateTasksNum(textArray, isBullet); + return this.calculateTasksNum(textArray, isBullet, customGoal); } /** @@ -1275,7 +1282,8 @@ export function taskProgressBarExtension( public calculateTasksNum( textArray: string[], - bullet: boolean + bullet: boolean, + customGoal?: number | null ): Tasks { if (!textArray || textArray.length === 0) { return { @@ -1315,7 +1323,9 @@ export function taskProgressBarExtension( let planned: number = 0; let total: number = 0; let level: number = 0; - + let completedGoalValue: number = 0; + let totalGoalValue: number = 0; + // Get tab size from vault config const tabSize = this.getTabSize(); @@ -1324,6 +1334,7 @@ export function taskProgressBarExtension( mark: string; status: string; lineText: string; + goalValue?: number; }[] = []; // Determine indentation level for bullets @@ -1374,6 +1385,24 @@ export function taskProgressBarExtension( // Get the task status const status = this.getTaskStatus(lineTextTrimmed); + // Extract task text to check for task-specific goal + const taskText = TaskGoalManager.extractTaskText(lineTextTrimmed); + let taskGoalValue = 0; + + if (taskText) { + // Check for task-specific goal + const taskGoal = TaskGoalManager.extractTaskSpecificGoal(taskText); + if (taskGoal !== null) { + taskGoalValue = taskGoal; + totalGoalValue += taskGoalValue; + + // If task is completed, add its goal value to completedGoalValue + if (status === "completed") { + completedGoalValue += taskGoalValue; + } + } + } + // Extract the mark for debugging const markMatch = lineTextTrimmed.match(/\[(.)]/); if (markMatch && markMatch[1]) { @@ -1381,6 +1410,7 @@ export function taskProgressBarExtension( mark: markMatch[1], status: status, lineText: lineTextTrimmed, + goalValue: taskGoalValue > 0 ? taskGoalValue : undefined }); } @@ -1450,7 +1480,7 @@ export function taskProgressBarExtension( } } - // Ensure counts don't exceed total + // Ensure counts don't exceed total completed = Math.min(completed, total); inProgress = Math.min(inProgress, total - completed); abandoned = Math.min(abandoned, total - completed - inProgress); @@ -1460,15 +1490,26 @@ export function taskProgressBarExtension( ); notStarted = total - completed - inProgress - abandoned - planned; - - return { + + // Use the TaskGoalManager to adjust counts based on goals + const taskCounts = { completed, total, inProgress, abandoned, notStarted, - planned, + planned }; + + // Apply goal-based adjustments using the TaskGoalManager + const adjustedCounts = TaskGoalManager.adjustTaskCountsForGoals( + taskCounts, + customGoal ? customGoal : null, + completedGoalValue, + totalGoalValue + ); + + return adjustedCounts; } }, { diff --git a/src/utils/goal/main.ts b/src/utils/goal/main.ts new file mode 100644 index 00000000..31f7cc64 --- /dev/null +++ b/src/utils/goal/main.ts @@ -0,0 +1,138 @@ +/** + * Goal Tracking System + * + * This module handles goal-related functionality for tasks in Obsidian Task Genius. + * It supports extracting goal information from task text using g::number and goal::number formats. + */ + +/** + * Represents a collection of task-specific goal values + */ +export interface TaskGoalValues { + /** Sum of goal values from completed tasks */ + completedGoalValue: number; + /** Sum of all goal values from tasks */ + totalGoalValue: number; +} + +/** + * Class that manages all goal-related functionality + */ +export class TaskGoalManager { + /** + * Extract the text content of a task from a markdown line + * + * @param lineText The full text of the markdown line containing the task + * @return The extracted task text or null if no task was found + */ + static extractTaskText(lineText: string): string | null { + if (!lineText) return null; + + const taskTextMatch = lineText.match(/^[\s|\t]*([-*+]|\d+\.)\s\[(.)\]\s*(.*?)$/); + if (taskTextMatch && taskTextMatch[3]) { + return taskTextMatch[3].trim(); + } + + return null; + } + + static isGoalActive(taskText: string): boolean { + if (!taskText) return false; + + // Check for the presence of g:: or goal:: in the task text + const goalMatch = taskText.match(/\b(g|goal)::(\d+)\b/i); + return !!goalMatch; + } + + /** + * Extract the goal value from a task text + * Supports only g::number or goal::number format + * + * @param taskText The task text to extract the goal from + * @return The extracted goal value or null if no goal found + */ + static extractTaskSpecificGoal(taskText: string): number | null { + if (!taskText) return null; + + // Match only the patterns g::number or goal::number + const goalMatch = taskText.match(/\b(g|goal)::(\d+)\b/i); + if (!goalMatch) return null; + + return Number(goalMatch[2]); + } + + /** + * Extract task text and goal information from a line + * + * @param lineText The full text of the markdown line containing the task + * @return The extracted goal value or null if no goal found + */ + static extractTaskAndGoalInfo(lineText: string | null): number | null { + if (!lineText) return null; + + // Extract task text + const taskText = TaskGoalManager.extractTaskText(lineText); + if (!taskText) return null; + + // Check for goal in g::number or goal::number format + return TaskGoalManager.extractTaskSpecificGoal(taskText); + } + + /** + * Adjust task counts based on goal values + * + * @param taskCounts Current task count values + * @param customGoal Custom goal total value + * @param completedGoalValue Sum of goal values from completed tasks + * @param totalGoalValue Sum of all goal values + * @return Updated task counts with goal-based adjustments + */ + static adjustTaskCountsForGoals( + taskCounts: { + completed: number; + total: number; + inProgress?: number; + abandoned?: number; + notStarted?: number; + planned?: number; + }, + customGoal: number | null, + completedGoalValue: number, + totalGoalValue: number + ) { + const { completed, total, inProgress = 0, abandoned = 0, planned = 0 } = taskCounts; + let adjustedCounts = { ...taskCounts }; + + // If we have a custom goal specified with g::number format, use it as the total + if (customGoal !== null) { + // Always set the total to be the custom goal when specified + adjustedCounts.total = customGoal; + + // If we also have task-specific goals, use the goal values to calculate completion + if (totalGoalValue > 0) { + // Calculate the percentage of completion based on goal values + const completionRatio = completedGoalValue / customGoal; + console.log("Completion ratio:", completionRatio); + console.log("Custom goal:", customGoal); + + // Calculate completed count based on the goal completion ratio + adjustedCounts.completed = Math.round(completionRatio * customGoal); + adjustedCounts.completed = Math.min(adjustedCounts.completed, customGoal); // Ensure it doesn't exceed total + + // Adjust other counts proportionally + adjustedCounts.notStarted = customGoal - adjustedCounts.completed - inProgress - abandoned - (planned || 0); + adjustedCounts.notStarted = Math.max(0, adjustedCounts.notStarted); // Ensure it's not negative + } else { + // If no task-specific goals, use the completed tasks against the custom total goal + adjustedCounts.completed = Math.min(completed, customGoal); + + // Adjust notStarted to make the math work out with the new total + adjustedCounts.notStarted = customGoal - adjustedCounts.completed - inProgress - abandoned - (planned || 0); + adjustedCounts.notStarted = Math.max(0, adjustedCounts.notStarted); + } + } + console.log("Adjusted task counts for goals:", adjustedCounts); + + return adjustedCounts; + } +} \ No newline at end of file