From 5aa51986fe4ba0e421d2a7b5ae786fb9cc125e98 Mon Sep 17 00:00:00 2001 From: Luke Leppan Date: Thu, 8 Jul 2021 22:20:38 +0200 Subject: [PATCH] Add Daily Counts #11 --- README.md | 24 +++-- src/data/collector.ts | 4 +- src/data/manager.ts | 190 +++++++++++++++++++++++++---------- src/main.ts | 94 +++++++++-------- src/settings/settings-tab.ts | 14 ++- src/status/manager.ts | 50 +++++++-- 6 files changed, 262 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 63ec8c2..cdca81c 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,26 @@ ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/lukeleppan/better-word-count/Build%20Release?logo=github&style=for-the-badge) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/lukeleppan/better-word-count?style=for-the-badge) ![GitHub All Releases](https://img.shields.io/github/downloads/lukeleppan/better-word-count/total?style=for-the-badge) -This plugin is the same as the built-in **Word Count** plugin, except when you select text, it will count the selected word instead of the whole document. I recommend turning off the built-in **Word Count** because this plugin is designed to replace that. +This plugin is the same as the built-in **Word Count** plugin, except when you select text, it will count the selected word instead of the whole document. I recommend turning off the built-in **Word Count** because this plugin is designed to replace that. This plugin also has the ability to store statistics about your vault. ![Better Count Word](https://raw.githubusercontent.com/lukeleppan/better-word-count/master/assets/better-word-count.gif) ### Features -All Optional: - -- Show word count (total when no text selected). -- Show character count. -- Show sentence count. -- Show file count (on non-markdown windows). - -Works with all languages. +- Allows you to store statistics about your vault. +- Works with all languages. +- Can display a variety of different stats. Including: + - Words, Characters and Sentences in current file. + - Total Words, Characters and Sentences in vault. + - Words, Characters and Sentences typed today. + - Total Files in vault. +- Highly Customizable status bar that can be adapted to your needs. #### TODO: -- [ ] more statistic display +- [ ] add statistic view - [ ] add more statistics (make suggestions) +- [ ] add goals ### Contributors @@ -36,3 +37,6 @@ Works with all languages. - @aproximate - @Quorafind - @torantine +- @lammersma +- @aknighty74 +- @dhruvik7 diff --git a/src/data/collector.ts b/src/data/collector.ts index a8528a8..dff837d 100644 --- a/src/data/collector.ts +++ b/src/data/collector.ts @@ -1,6 +1,6 @@ import moment from "moment"; import type { MetadataCache, TFile, Vault } from "obsidian"; -import { getCharacterCount, getWordCount } from "./stats"; +import { getCharacterCount, getSentenceCount, getWordCount } from "./stats"; export class DataCollector { private vault: Vault; @@ -42,7 +42,7 @@ export class DataCollector { const files = this.vault.getFiles(); for (const i in files) { const file = files[i]; - sentence += getCharacterCount(await this.vault.cachedRead(file)); + sentence += getSentenceCount(await this.vault.cachedRead(file)); } return sentence; diff --git a/src/data/manager.ts b/src/data/manager.ts index f4095d8..187f690 100644 --- a/src/data/manager.ts +++ b/src/data/manager.ts @@ -1,14 +1,44 @@ import moment from "moment"; -import type { MetadataCache, TAbstractFile, Vault } from "obsidian"; +import type { MetadataCache, TFile, Vault } from "obsidian"; import { STATS_FILE } from "src/constants"; import { DataCollector } from "./collector"; +import { getCharacterCount, getSentenceCount, getWordCount } from "./stats"; + +type History = Record; + +interface Day { + files: number; + modifiedFiles: ModFiles; + words: number; + characters: number; + sentences: number; + totalWords: number; + totalCharacters: number; + totalSentences: number; +} + +type ModFiles = Record; + +type FileStats = Record; + +interface Count { + initial: number; + current: number; +} + +export interface TodayCounts { + words: number; + characters: number; + sentences: number; +} export class DataManager { private vault: Vault; private metadataCache: MetadataCache; - private stats: any; - private index: number; + private history: History; + private today: string; private collector: DataCollector; + private todayCounts: TodayCounts; constructor(vault: Vault, metadataCache: MetadataCache) { this.vault = vault; @@ -17,71 +47,127 @@ export class DataManager { this.vault.adapter.exists(".vault-stats").then(async (exists) => { if (!exists) { - const json: string = JSON.stringify({ - history: [], - }); - - this.vault.adapter.write(".vault-stats", json); + this.vault.adapter.write(".vault-stats", "{}"); } - this.stats = JSON.parse(await this.vault.adapter.read(".vault-stats")); - this.getTodayIndex(); + this.history = Object.assign( + JSON.parse(await this.vault.adapter.read(".vault-stats")) + ); + this.updateToday(); this.update(); }); } async update(): Promise { - this.vault.adapter.write(STATS_FILE, JSON.stringify(this.stats)); + this.vault.adapter.write(STATS_FILE, JSON.stringify(this.history)); } - getTodayIndex(): void { - const length: number = this.stats.history.length; + async updateToday(): Promise { + const newDay: Day = { + files: this.collector.getTotalFileCount(), + modifiedFiles: {}, + words: 0, + characters: 0, + sentences: 0, + totalWords: await this.collector.getTotalWordCount(), + totalCharacters: await this.collector.getTotalCharacterCount(), + totalSentences: await this.collector.getTotalSentenceCount(), + }; - if (length === 0) { - this.index = - this.stats.history.push({ - date: moment().format("YYYY-MM-DD"), - initFiles: 0, - finalFiles: 0, - modifiedFiles: [], - words: 0, - characters: 0, - sentences: 0, - totalWords: 0, - totalCharacters: 0, - totalSentences: 0, - }) - 1; - } else if ( - this.stats.history[this.stats.history.length - 1].date === - moment().format("YYYY-MM-DD") + if (!this.history.hasOwnProperty(moment().format("YYYY-MM-DD"))) { + this.history[moment().format("YYYY-MM-DD")] = newDay; + } + + this.today = moment().format("YYYY-MM-DD"); + + this.update(); + } + + async setTotalStats() { + this.history[this.today].files = this.collector.getTotalFileCount(); + this.history[this.today].totalWords = + await this.collector.getTotalWordCount(); + this.history[this.today].totalCharacters = + await this.collector.getTotalCharacterCount(); + this.history[this.today].totalSentences = + await this.collector.getTotalSentenceCount(); + this.update(); + } + + change(file: TFile, data: string) { + const currentWords = getWordCount(data); + const currentCharacters = getCharacterCount(data); + const currentSentences = getSentenceCount(data); + + if ( + this.history.hasOwnProperty(this.today) && + this.today === moment().format("YYYY-MM-DD") ) { - this.index = this.stats.history.length - 1; - } else { - this.index = - this.stats.history.push({ - date: moment().format("YYYY-MM-DD"), - initFiles: 0, - finalFiles: 0, - modifiedFiles: [], - words: 0, - characters: 0, - sentences: 0, - totalWords: 0, - totalCharacters: 0, - totalSentences: 0, - }) - 1; - } - } + if (!this.history[this.today].modifiedFiles.hasOwnProperty(file.path)) { + const newWordCount: Count = { + initial: currentWords, + current: currentWords, + }; + const newCharacterCount: Count = { + initial: currentCharacters, + current: currentCharacters, + }; + const newSentenceCount: Count = { + initial: currentSentences, + current: currentSentences, + }; + const fileStats: FileStats = { + 0: newWordCount, + 1: newCharacterCount, + 2: newSentenceCount, + }; - onVaultModify(file: TAbstractFile) { - if (!this.stats.history[this.index].modifiedFiles.includes(file.name)) { - this.stats.history[this.index].modifiedFiles.push(file.name); + this.history[this.today].modifiedFiles[file.path] = fileStats; + } else { + this.history[this.today].modifiedFiles[file.path][0].current = + currentWords; + this.history[this.today].modifiedFiles[file.path][1].current = + currentCharacters; + this.history[this.today].modifiedFiles[file.path][2].current = + currentSentences; + } + this.updateTodayCounts(); this.update(); + } else { + this.updateToday(); } } - change(cm: CodeMirror.Editor) {} + updateTodayCounts() { + const words = Object.values(this.history[this.today].modifiedFiles) + .map((counts) => Math.max(0, counts[0].current - counts[0].initial)) + .reduce((a, b) => a + b, 0); + const characters = Object.values(this.history[this.today].modifiedFiles) + .map((counts) => Math.max(0, counts[1].current - counts[1].initial)) + .reduce((a, b) => a + b, 0); + const sentences = Object.values(this.history[this.today].modifiedFiles) + .map((counts) => Math.max(0, counts[2].current - counts[2].initial)) + .reduce((a, b) => a + b, 0); - setTotalStats() {} + this.history[this.today].words = words; + this.history[this.today].characters = characters; + this.history[this.today].sentences = sentences; + + this.todayCounts = { + words: words, + characters: characters, + sentences: sentences, + }; + } + + getTodayCounts(): TodayCounts { + return this.todayCounts; + } + + async updateFromFile() { + this.history = Object.assign( + JSON.parse(await this.vault.adapter.read(".vault-stats")) + ); + } } diff --git a/src/main.ts b/src/main.ts index 3b0cc72..647bb2c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,7 @@ import { BetterWordCountSettingsTab } from "./settings/settings-tab"; import { BetterWordCountSettings, DEFAULT_SETTINGS } from "./settings/settings"; import { StatusBar } from "./status/bar"; import { STATS_ICON, STATS_ICON_NAME, VIEW_TYPE_STATS } from "./constants"; -import StatsView from "./view/view"; +// import StatsView from "./view/view"; import { DataManager } from "./data/manager"; import { BarManager } from "./status/manager"; import type CodeMirror from "codemirror"; @@ -12,7 +12,7 @@ export default class BetterWordCount extends Plugin { public statusBar: StatusBar; public currentFile: TFile; public settings: BetterWordCountSettings; - public view: StatsView; + // public view: StatsView; public dataManager: DataManager; public barManager: BarManager; @@ -24,13 +24,10 @@ export default class BetterWordCount extends Plugin { async onload() { this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData()); - this.saveData(this.settings); this.addSettingTab(new BetterWordCountSettingsTab(this.app, this)); let statusBarEl = this.addStatusBarItem(); this.statusBar = new StatusBar(statusBarEl); - - this.dataManager = new DataManager(this.app.vault, this.app.metadataCache); this.barManager = new BarManager( this.statusBar, this.settings, @@ -38,16 +35,15 @@ export default class BetterWordCount extends Plugin { this.app.metadataCache ); - this.registerEvent( - this.app.workspace.on("active-leaf-change", this.activeLeafChange, this) - ); + if (this.settings.collectStats) { + this.dataManager = new DataManager( + this.app.vault, + this.app.metadataCache + ); + } this.registerEvent( - this.app.vault.on( - "modify", - this.dataManager.onVaultModify, - this.dataManager - ) + this.app.workspace.on("active-leaf-change", this.activeLeafChange, this) ); this.registerCodeMirror((cm: CodeMirror.Editor) => { @@ -56,27 +52,45 @@ export default class BetterWordCount extends Plugin { ); }); - addIcon(STATS_ICON_NAME, STATS_ICON); + if (this.settings.collectStats) { + this.registerEvent( + this.app.workspace.on( + "quick-preview", + this.dataManager.change, + this.dataManager + ) + ); - this.addCommand({ - id: "show-vault-stats-view", - name: "Open Statistics", - checkCallback: (checking: boolean) => { - if (checking) { - return this.app.workspace.getLeavesOfType("vault-stats").length === 0; - } - this.initLeaf(); - }, - }); - - this.registerView( - VIEW_TYPE_STATS, - (leaf: WorkspaceLeaf) => (this.view = new StatsView(leaf)) - ); - - if (this.app.workspace.layoutReady) { - this.initLeaf(); + this.registerInterval( + window.setInterval(() => { + this.dataManager.setTotalStats(); + }, 1000 * 60) + ); } + + // addIcon(STATS_ICON_NAME, STATS_ICON); + + // this.addCommand({ + // id: "show-vault-stats-view", + // name: "Open Statistics", + // checkCallback: (checking: boolean) => { + // if (checking) { + // return this.app.workspace.getLeavesOfType("vault-stats").length === 0; + // } + // this.initLeaf(); + // }, + // }); + + // this.registerView( + // VIEW_TYPE_STATS, + // (leaf: WorkspaceLeaf) => (this.view = new StatsView(leaf)) + // ); + + // if (this.app.workspace.layoutReady) { + // this.initLeaf(); + // } else { + // this.app.workspace.onLayoutReady(() => this.initLeaf()); + // } } activeLeafChange(leaf: WorkspaceLeaf) { @@ -89,12 +103,12 @@ export default class BetterWordCount extends Plugin { await this.saveData(this.settings); } - initLeaf(): void { - if (this.app.workspace.getLeavesOfType(VIEW_TYPE_STATS).length) { - return; - } - this.app.workspace.getRightLeaf(false).setViewState({ - type: VIEW_TYPE_STATS, - }); - } + // initLeaf(): void { + // if (this.app.workspace.getLeavesOfType(VIEW_TYPE_STATS).length) { + // return; + // } + // this.app.workspace.getRightLeaf(false).setViewState({ + // type: VIEW_TYPE_STATS, + // }); + // } } diff --git a/src/settings/settings-tab.ts b/src/settings/settings-tab.ts index eba9baf..6b9f4e4 100644 --- a/src/settings/settings-tab.ts +++ b/src/settings/settings-tab.ts @@ -29,7 +29,7 @@ export class BetterWordCountSettingsTab extends PluginSettingTab { new Setting(containerEl) .setName("Collect Statistics") .setDesc( - "Turn on to start collecting daily statistics of your writing. Stored in the .vault-stats file in the root of your vault." + "Reload Required for change to take effect. Turn on to start collecting daily statistics of your writing. Stored in the .vault-stats file in the root of your vault. This is required for counts of the day." ) .addToggle((cb: ToggleComponent) => { cb.setValue(this.plugin.settings.collectStats); @@ -39,8 +39,8 @@ export class BetterWordCountSettingsTab extends PluginSettingTab { }); }); new Setting(containerEl) - .setName("Count Comments") - .setDesc("Turn off if you don't want markdown comments to be counted.") + .setName("Don't Count Comments") + .setDesc("Turn on if you don't want markdown comments to be counted.") .addToggle((cb: ToggleComponent) => { cb.setValue(this.plugin.settings.countComments); cb.onChange(async (value: boolean) => { @@ -76,6 +76,12 @@ export class BetterWordCountSettingsTab extends PluginSettingTab { .addTextArea((cb: TextAreaComponent) => { cb.setPlaceholder("Enter an expression..."); cb.setValue(this.plugin.settings.statusBarQuery); + cb.onChange((value: string) => { + let newPreset = PRESETS.find((preset) => preset.name === "custom"); + this.plugin.settings.preset = newPreset; + this.plugin.settings.statusBarQuery = value; + this.plugin.saveSettings(); + }); }); new Setting(containerEl) .setName("Alternative Status Bar Text") @@ -84,6 +90,8 @@ export class BetterWordCountSettingsTab extends PluginSettingTab { cb.setPlaceholder("Enter an expression..."); cb.setValue(this.plugin.settings.statusBarAltQuery); cb.onChange((value: string) => { + let newPreset = PRESETS.find((preset) => preset.name === "custom"); + this.plugin.settings.preset = newPreset; this.plugin.settings.statusBarAltQuery = value; this.plugin.saveSettings(); }); diff --git a/src/status/manager.ts b/src/status/manager.ts index 0c9d09b..6cd2cfb 100644 --- a/src/status/manager.ts +++ b/src/status/manager.ts @@ -1,5 +1,7 @@ import type { MetadataCache, Vault } from "obsidian"; import { DataCollector } from "src/data/collector"; +import { DataManager } from "src/data/manager"; +import type { TodayCounts } from "src/data/manager"; import type { BetterWordCountSettings } from "src/settings/settings"; import { getWordCount, @@ -15,6 +17,7 @@ export class BarManager { private settings: BetterWordCountSettings; private vault: Vault; private dataCollector: DataCollector; + private dataManager: DataManager; constructor( statusBar: StatusBar, @@ -26,15 +29,21 @@ export class BarManager { this.settings = settings; this.vault = vault; this.dataCollector = new DataCollector(vault, metadataCache); + this.dataManager = new DataManager(vault, metadataCache); } async updateStatusBar(text: string): Promise { let newText = ""; const expression: Expression = parse(this.settings.statusBarQuery); + if (this.settings.collectStats) this.dataManager.updateTodayCounts(); + const todayCounts: TodayCounts = this.settings.collectStats + ? this.dataManager.getTodayCounts() + : { words: 0, characters: 0, sentences: 0 }; let varsIndex = 0; - expression.parsed.forEach((value: string) => { - newText = newText + value; + for (const i in expression.parsed) { + const e = expression.parsed[i]; + newText = newText + e; switch (expression.vars[varsIndex]) { case 0: newText = newText + getWordCount(text); @@ -46,20 +55,31 @@ export class BarManager { newText = newText + getSentenceCount(text); break; case 3: - newText = newText + 0; + newText = newText + (await this.dataCollector.getTotalWordCount()); break; case 4: - newText = newText + 0; + newText = + newText + (await this.dataCollector.getTotalCharacterCount()); break; case 5: - newText = newText + 0; + newText = + newText + (await this.dataCollector.getTotalCharacterCount()); break; case 6: - newText = newText + 0; + newText = newText + this.dataCollector.getTotalFileCount(); + break; + case 7: + newText = newText + todayCounts.words; + break; + case 8: + newText = newText + todayCounts.characters; + break; + case 9: + newText = newText + todayCounts.sentences; break; } varsIndex++; - }); + } this.statusBar.displayText(newText); } @@ -67,6 +87,10 @@ export class BarManager { async updateAltStatusBar(): Promise { let newText = ""; const expression: Expression = parse(this.settings.statusBarAltQuery); + if (this.settings.collectStats) this.dataManager.updateTodayCounts(); + const todayCounts: TodayCounts = this.settings.collectStats + ? this.dataManager.getTodayCounts() + : { words: 0, characters: 0, sentences: 0 }; let varsIndex = 0; for (const i in expression.parsed) { @@ -96,6 +120,15 @@ export class BarManager { case 6: newText = newText + this.dataCollector.getTotalFileCount(); break; + case 7: + newText = newText + todayCounts.words; + break; + case 8: + newText = newText + todayCounts.characters; + break; + case 9: + newText = newText + todayCounts.sentences; + break; } varsIndex++; } @@ -111,6 +144,9 @@ export class BarManager { this.updateStatusBar(cm.getSelection()); } } else { + if (this.settings.collectStats) { + this.dataManager.updateFromFile(); + } if (this.settings.countComments) { this.updateStatusBar(cleanComments(cm.getValue())); } else {