diff --git a/src/Logger.ts b/src/Logger.ts index e2d3f69..5343636 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -2,7 +2,7 @@ import { type TimerState, type Mode } from 'Timer' import * as utils from 'utils' import PomodoroTimerPlugin from 'main' import { TFile, Notice, moment } from 'obsidian' -import { incrTaskActual, type TaskItem } from 'Tasks' +import { type TaskItem } from 'Tasks' export type TimerLog = { duration: number @@ -56,29 +56,6 @@ export default class Logger { } } - // update task item - if (this.plugin.getSettings().enableTaskTracking) { - if ( - state.mode === 'WORK' && - log.finished && - state.task?.path && - state.task?.blockLink - ) { - let file = this.plugin.app.vault.getAbstractFileByPath( - state.task.path, - ) - if (file && file instanceof TFile) { - let f = file as TFile - incrTaskActual( - this.plugin.getSettings().taskFormat, - this.plugin.app, - state.task.blockLink, - f, - ) - } - } - } - return logFile } diff --git a/src/TaskTracker.ts b/src/TaskTracker.ts index 2bd3afa..9db02e3 100644 --- a/src/TaskTracker.ts +++ b/src/TaskTracker.ts @@ -1,12 +1,14 @@ -import type { TaskItem } from 'Tasks' +import { type TaskItem } from 'Tasks' import type PomodoroTimerPlugin from 'main' -import { TFile, Keymap } from 'obsidian' +import { TFile, Keymap, MarkdownView } from 'obsidian' +import { DESERIALIZERS, POMODORO_REGEX } from 'serializer' import { writable, type Readable, type Writable, type Unsubscriber, } from 'svelte/store' +import { extractTaskComponents } from 'utils' export type TaskTrackerState = { task?: TaskItem @@ -111,7 +113,7 @@ export default class TaskTracker implements TaskTrackerStore { if (task.blockLink) { if (!line.endsWith(task.blockLink)) { // block id mismatch? - lines[task.line] += ` ${task.blockLink}` + lines[task.line] += `${task.blockLink}` this.plugin.app.vault.modify(f, lines.join('\n')) return } @@ -119,7 +121,7 @@ export default class TaskTracker implements TaskTrackerStore { // generate block id let blockId = this.createBlockId() task.blockLink = blockId - lines[task.line] += ` ${blockId}` + lines[task.line] += `${blockId}` this.plugin.app.vault.modify(f, lines.join('\n')) } } @@ -183,4 +185,99 @@ export default class TaskTracker implements TaskTrackerStore { }) } } + + public async updateActual() { + // update task item + if ( + this.plugin.getSettings().enableTaskTracking && + this.task && + this.task.blockLink + ) { + let file = this.plugin.app.vault.getAbstractFileByPath( + this.task.path, + ) + if (file && file instanceof TFile) { + let f = file as TFile + if (this.task?.actual) { + this.task.actual += 1 + } else { + this.task.actual = 1 + } + + await this.incrTaskActual(this.task.blockLink, f) + } + } + } + + private async incrTaskActual(blockLink: string, file: TFile) { + const format = this.plugin.getSettings().taskFormat + + if (file.extension !== 'md') { + return + } + + let metadata = this.plugin.app.metadataCache.getFileCache(file) + let content = await this.plugin.app.vault.read(file) + + if (!content || !metadata) { + return + } + + const lines = content.split('\n') + + for (let rawElement of metadata.listItems || []) { + if (rawElement.task) { + let lineNr = rawElement.position.start.line + let line = lines[lineNr] + + const components = extractTaskComponents(line) + + console.log(components) + if (!components) { + continue + } + + if (components.blockLink === blockLink) { + console.log('update') + const match = components.body.match(POMODORO_REGEX) + if (match !== null) { + let pomodoros = match[1] + let [actual = '0', expected] = pomodoros.split('/') + let text = `🍅:: ${parseInt(actual) + 1}` + if (expected !== undefined) { + text += `/${expected.trim()}` + } + line = line + .replace(/🍅:: *(\d* *\/? *\d* *)/, text) + .trim() + lines[lineNr] = line + } else { + let detail = DESERIALIZERS[format].deserialize( + components.body, + ) + line = line.replace( + detail.description, + `${detail.description} [🍅:: 1]`, + ) + + lines[lineNr] = line + } + + this.plugin.app.vault.modify(file, lines.join('\n')) + this.plugin.app.metadataCache.trigger( + 'changed', + file, + content, + metadata, + ) + + // refresh view + this.plugin.app.workspace + .getActiveViewOfType(MarkdownView) + ?.load() + break + } + } + } + } } diff --git a/src/Tasks.ts b/src/Tasks.ts index 9f96b2c..cd4816f 100644 --- a/src/Tasks.ts +++ b/src/Tasks.ts @@ -3,20 +3,10 @@ import { type CachedMetadata, type TFile, type App } from 'obsidian' import { extractTaskComponents } from 'utils' import { writable, derived, type Readable, type Writable } from 'svelte/store' -import { - DataviewTaskSerializer, - DefaultTaskSerializer, - type TaskDeserializer, - DEFAULT_SYMBOLS, -} from 'serializer' import type { TaskFormat } from 'Settings' import type { Unsubscriber } from 'svelte/motion' import { MarkdownView } from 'obsidian' - -const DESERIALIZERS: Record = { - TASKS: new DefaultTaskSerializer(DEFAULT_SYMBOLS), - DATAVIEW: new DataviewTaskSerializer(), -} +import { DESERIALIZERS, POMODORO_REGEX } from 'serializer' export type TaskItem = { path: string @@ -159,73 +149,6 @@ export default class Tasks implements Readable { } } -const POMODORO_REGEX = new RegExp( - '(?:(?=[^\\]]+\\])\\[|(?=[^)]+\\))\\() *🍅:: *(\\d* *\\/? *\\d*) *[)\\]](?: *,)?', -) - -export async function incrTaskActual( - format: TaskFormat, - app: App, - blockLink: string, - file: TFile, -) { - if (file.extension !== 'md') { - return - } - - let metadata = app.metadataCache.getFileCache(file) - let content = await app.vault.read(file) - - if (!content || !metadata) { - return - } - - const lines = content.split('\n') - - for (let rawElement of metadata.listItems || []) { - if (rawElement.task) { - let lineNr = rawElement.position.start.line - let line = lines[lineNr] - - const components = extractTaskComponents(line) - if (!components) { - continue - } - - if (components.blockLink === blockLink) { - const match = components.body.match(POMODORO_REGEX) - if (match !== null) { - let pomodoros = match[1] - let [actual = '0', expected] = pomodoros.split('/') - let text = `🍅:: ${parseInt(actual) + 1}` - if (expected !== undefined) { - text += `/${expected.trim()}` - } - line = line.replace(/🍅:: *(\d* *\/? *\d* *)/, text).trim() - lines[lineNr] = line - } else { - let detail = DESERIALIZERS[format].deserialize( - components.body, - ) - line = line.replace( - detail.description, - `${detail.description} [🍅:: 1]`, - ) - - lines[lineNr] = line - } - - app.vault.modify(file, lines.join('\n')) - app.metadataCache.trigger('changed', file, content, metadata) - - // refresh view - app.workspace.getActiveViewOfType(MarkdownView)?.load() - break - } - } - } -} - export function resolveTasks( format: TaskFormat, file: TFile, @@ -258,7 +181,7 @@ export function resolveTasks( fileName: file.name, name: detail.description, status: components.status, - blockLink: components.blockLink.trim(), + blockLink: components.blockLink, checked: rawElement.task != '' && rawElement.task != ' ', description: detail.description, done: detail.doneDate?.format(dateformat), diff --git a/src/Timer.ts b/src/Timer.ts index a4a061d..05a882c 100644 --- a/src/Timer.ts +++ b/src/Timer.ts @@ -29,7 +29,10 @@ export type TimerState = { duration: number } -export type TimerStore = TimerState & { remained: TimerRemained } +export type TimerStore = TimerState & { + remained: TimerRemained + finished: boolean +} export default class Timer implements Readable { static DEFAULT_NOTIFICATION_AUDIO = new Audio(DEFAULT_NOTIFICATION) @@ -75,6 +78,7 @@ export default class Timer implements Readable { this.store = derived(store, ($state) => ({ ...$state, remained: this.remain($state.count, $state.elapsed), + finished: $state.count == $state.elapsed, })) this.subscribe = this.store.subscribe @@ -136,6 +140,11 @@ export default class Timer implements Readable { task.actual = task.actual ? task.actual + 1 : 1 } const s = { ...state, task } + + if (state.mode == 'WORK') { + this.plugin.tracker?.updateActual() + } + this.logger.log(s).then((logFile) => { this.notify(s, logFile) }) diff --git a/src/serializer/index.ts b/src/serializer/index.ts index d0b8f40..93d3be8 100644 --- a/src/serializer/index.ts +++ b/src/serializer/index.ts @@ -1,4 +1,6 @@ -import type { Priority } from './TaskModels' +import type { TaskFormat } from 'Settings' +import { DataviewTaskSerializer } from './DataviewTaskSerializer' +import { DefaultTaskSerializer, DEFAULT_SYMBOLS } from './DefaultTaskSerializer' import type { Moment } from 'moment' /** * A subset of fields of {@link Task} that can be parsed from the textual @@ -16,7 +18,7 @@ export type TaskDetails = { doneDate: Moment | null cancelledDate: Moment | null recurrenceRule: string - pomodoros: string + pomodoros: string tags: string[] } @@ -49,3 +51,12 @@ export interface TaskDeserializer { export { DefaultTaskSerializer, DEFAULT_SYMBOLS } from './DefaultTaskSerializer' export { DataviewTaskSerializer } from './DataviewTaskSerializer' + +export const POMODORO_REGEX = new RegExp( + '(?:(?=[^\\]]+\\])\\[|(?=[^)]+\\))\\() *🍅:: *(\\d* *\\/? *\\d*) *[)\\]](?: *,)?', +) + +export const DESERIALIZERS: Record = { + TASKS: new DefaultTaskSerializer(DEFAULT_SYMBOLS), + DATAVIEW: new DataviewTaskSerializer(), +} diff --git a/src/utils.ts b/src/utils.ts index dba0155..2d517f6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -55,7 +55,7 @@ export const ensureFileExists = async ( if (dirs.length) { const dir = join(...dirs) - if (app.vault.getAbstractFileByPath(dir)) { + if (!app.vault.getAbstractFileByPath(dir)) { await app.vault.createFolder(dir) } }