mirror of
https://github.com/dralkh/spaceforge.git
synced 2026-07-22 16:40:30 +00:00
189 lines
6.1 KiB
TypeScript
189 lines
6.1 KiB
TypeScript
import { TFile } from 'obsidian';
|
|
import SpaceforgePlugin from '../main';
|
|
|
|
/**
|
|
* Utility functions for time estimation
|
|
*/
|
|
export class EstimationUtils {
|
|
/**
|
|
* Reading speeds for different content types (words per minute)
|
|
* Used as fallback and for content-specific adjustments
|
|
*/
|
|
private static readonly READING_SPEEDS = {
|
|
notes: 200, // General notes
|
|
technical: 100, // Technical content
|
|
fiction: 250, // Fiction/prose
|
|
simple: 300 // Simple content
|
|
};
|
|
|
|
/**
|
|
* Average English word length in characters (including spaces)
|
|
*/
|
|
private static readonly AVG_WORD_LENGTH = 5.5;
|
|
|
|
/**
|
|
* Minimum review time in seconds
|
|
*/
|
|
private static readonly MIN_REVIEW_TIME = 30;
|
|
|
|
/**
|
|
* Reference to the plugin (for access to settings)
|
|
*/
|
|
private static plugin: SpaceforgePlugin;
|
|
|
|
/**
|
|
* Set the plugin reference
|
|
*
|
|
* @param plugin Reference to the main plugin
|
|
*/
|
|
static setPlugin(plugin: SpaceforgePlugin): void {
|
|
this.plugin = plugin;
|
|
}
|
|
|
|
/**
|
|
* Get the user's reading speed from settings
|
|
*
|
|
* @param contentType Optional content type for adjustment
|
|
* @returns Reading speed in words per minute
|
|
*/
|
|
static getReadingSpeed(contentType?: keyof typeof EstimationUtils.READING_SPEEDS): number {
|
|
// Get base reading speed from settings
|
|
const baseSpeed = this.plugin?.settings.readingSpeed || 200;
|
|
|
|
// Apply content-specific adjustment if specified
|
|
if (contentType) {
|
|
const baseContentSpeed = this.READING_SPEEDS.notes;
|
|
const contentSpeed = this.READING_SPEEDS[contentType];
|
|
return baseSpeed * (contentSpeed / baseContentSpeed);
|
|
}
|
|
|
|
return baseSpeed;
|
|
}
|
|
|
|
/**
|
|
* Estimate review time for a file based on its content
|
|
*
|
|
* @param file The file to estimate review time for
|
|
* @param fileContent Optional file content (to avoid reading file again)
|
|
* @param contentType Type of content for reading speed adjustment
|
|
* @returns Estimated review time in seconds
|
|
*/
|
|
static estimateReviewTime(
|
|
file: TFile,
|
|
fileContent?: string,
|
|
contentType: keyof typeof EstimationUtils.READING_SPEEDS = 'notes'
|
|
): number {
|
|
if (!file) {
|
|
return this.MIN_REVIEW_TIME;
|
|
}
|
|
|
|
// Use file size as a rough proxy if content is not available
|
|
if (!fileContent) {
|
|
const sizeEstimate = Math.ceil(file.stat.size / (this.AVG_WORD_LENGTH * 7)) * 60;
|
|
return Math.max(this.MIN_REVIEW_TIME, sizeEstimate);
|
|
}
|
|
|
|
// Count words in content
|
|
const wordCount = this.countWords(fileContent);
|
|
|
|
// Calculate reading time based on words and reading speed
|
|
const readingSpeed = this.getReadingSpeed(contentType);
|
|
const readingTimeMinutes = wordCount / readingSpeed;
|
|
|
|
// Add some buffer time for actual review (thinking, interacting)
|
|
const reviewTimeSeconds = Math.ceil(readingTimeMinutes * 60);
|
|
|
|
// Return at least minimum review time
|
|
return Math.max(this.MIN_REVIEW_TIME, reviewTimeSeconds);
|
|
}
|
|
|
|
/**
|
|
* Calculate aggregate review time for multiple notes
|
|
*
|
|
* @param paths Array of note paths
|
|
* @returns Total estimated review time in seconds
|
|
*/
|
|
static async calculateTotalReviewTime(paths: string[]): Promise<number> {
|
|
if (!this.plugin) {
|
|
return paths.length * this.MIN_REVIEW_TIME;
|
|
}
|
|
|
|
let totalTime = 0;
|
|
for (const path of paths) {
|
|
totalTime += await this.plugin.dataStorage.estimateReviewTime(path);
|
|
}
|
|
|
|
return totalTime;
|
|
}
|
|
|
|
/**
|
|
* Count words in text
|
|
*
|
|
* @param text Text to count words in
|
|
* @returns Number of words
|
|
*/
|
|
static countWords(text: string): number {
|
|
// Remove Markdown formatting
|
|
const cleanText = text
|
|
.replace(/```[\s\S]*?```/g, '') // Remove code blocks
|
|
.replace(/`.*?`/g, '') // Remove inline code
|
|
.replace(/\[.*?\]\(.*?\)/g, '') // Remove links
|
|
.replace(/\*\*.*?\*\*/g, '$1') // Bold to plain text
|
|
.replace(/\*.*?\*/g, '$1') // Italic to plain text
|
|
.replace(/~~.*?~~/g, '$1'); // Strikethrough to plain text
|
|
|
|
// Count words (sequences of non-whitespace characters)
|
|
const words = cleanText.match(/\S+/g) || [];
|
|
return words.length;
|
|
}
|
|
|
|
/**
|
|
* Format seconds as a readable time string
|
|
*
|
|
* @param seconds Time in seconds
|
|
* @returns Formatted time string (e.g., "5 min" or "1 hr 30 min")
|
|
*/
|
|
static formatTime(seconds: number): string {
|
|
const minutes = Math.floor(seconds / 60);
|
|
|
|
if (minutes < 60) {
|
|
return `${minutes} min`;
|
|
} else {
|
|
const hours = Math.floor(minutes / 60);
|
|
const remainingMinutes = minutes % 60;
|
|
|
|
if (remainingMinutes === 0) {
|
|
return `${hours} hr`;
|
|
} else {
|
|
return `${hours} hr ${remainingMinutes} min`;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format a time estimate with color coding based on duration
|
|
*
|
|
* @param seconds Time in seconds
|
|
* @param element HTML element to update
|
|
* @returns Formatted HTML time string with color coding
|
|
*/
|
|
static formatTimeWithColor(seconds: number, element: HTMLElement): void {
|
|
const formattedTime = this.formatTime(seconds);
|
|
element.setText(formattedTime);
|
|
|
|
// Color code based on duration
|
|
if (seconds < 5 * 60) { // Less than 5 minutes
|
|
element.addClass("review-time-short");
|
|
element.removeClass("review-time-medium");
|
|
element.removeClass("review-time-long");
|
|
} else if (seconds < 15 * 60) { // 5-15 minutes
|
|
element.addClass("review-time-medium");
|
|
element.removeClass("review-time-short");
|
|
element.removeClass("review-time-long");
|
|
} else { // More than 15 minutes
|
|
element.addClass("review-time-long");
|
|
element.removeClass("review-time-short");
|
|
element.removeClass("review-time-medium");
|
|
}
|
|
}
|
|
}
|