Add Daily Counts #11

This commit is contained in:
Luke Leppan 2021-07-08 22:20:38 +02:00
parent 7f817bd5bd
commit 5aa51986fe
6 changed files with 262 additions and 114 deletions

View file

@ -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

View file

@ -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;

View file

@ -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<string, Day>;
interface Day {
files: number;
modifiedFiles: ModFiles;
words: number;
characters: number;
sentences: number;
totalWords: number;
totalCharacters: number;
totalSentences: number;
}
type ModFiles = Record<string, FileStats>;
type FileStats = Record<number, Count>;
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<void> {
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<void> {
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"))
);
}
}

View file

@ -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,
// });
// }
}

View file

@ -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();
});

View file

@ -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<void> {
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<void> {
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 {