diff --git a/README.md b/README.md index 3944ddc..1fee0fb 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@ # Better Word Count -![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) ![Obsidian Downloads](https://img.shields.io/badge/dynamic/json?logo=obsidian&color=%23483699&label=downloads&query=%24%5B%22better-word-count%22%5D.downloads&url=https%3A%2F%2Fraw.githubusercontent.com%2Fobsidianmd%2Fobsidian-releases%2Fmaster%2Fcommunity-plugin-stats.json&style=for-the-badge) - -**IMPORTANT NOTICE:** Due to the introduction of the new Live Preview feature, this plugin needed to be almost entirely rewritten. It will be unstable for a while so please report bugs on github. - -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. +![GitHub manifest version](https://img.shields.io/github/manifest-json/v/lukeleppan/better-word-count?color=magenta&label=version&style=for-the-badge) ![GitHub Release Date](https://img.shields.io/github/release-date/lukeleppan/better-word-count?style=for-the-badge) ![Lines of code](https://img.shields.io/tokei/lines/github/lukeleppan/better-word-count?style=for-the-badge) ![Obsidian Downloads](https://img.shields.io/badge/dynamic/json?logo=obsidian&color=%23483699&label=downloads&query=%24%5B%22better-word-count%22%5D.downloads&url=https%3A%2F%2Fraw.githubusercontent.com%2Fobsidianmd%2Fobsidian-releases%2Fmaster%2Fcommunity-plugin-stats.json&style=for-the-badge) ![Better Count Word](https://raw.githubusercontent.com/lukeleppan/better-word-count/master/assets/better-word-count.gif) +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. + ## Features - Allows you to store statistics about your vault. diff --git a/src/editor/EditorPlugin.ts b/src/editor/EditorPlugin.ts index 28c429f..2626e34 100644 --- a/src/editor/EditorPlugin.ts +++ b/src/editor/EditorPlugin.ts @@ -1,3 +1,4 @@ +import { Transaction } from "@codemirror/state"; import { ViewUpdate, PluginValue, @@ -22,9 +23,19 @@ class EditorPlugin implements PluginValue { } const tr = update.transactions[0]; - if (!tr) return; + + if (!tr) { + return; + } + + // When selecting text with Shift+Home the userEventType is undefined. + // This is probably a bug in codemirror, for the time being doing an explict check + // for the type allows us to update the stats for the selection. + const userEventTypeUndefined = + tr.annotation(Transaction.userEvent) === undefined; + if ( - tr.isUserEvent("select") && + (tr.isUserEvent("select") || userEventTypeUndefined) && tr.newSelection.ranges[0].from !== tr.newSelection.ranges[0].to ) { let text = ""; diff --git a/src/main.ts b/src/main.ts index 5cf97a8..cbc3293 100644 --- a/src/main.ts +++ b/src/main.ts @@ -33,7 +33,7 @@ export default class BetterWordCount extends Plugin { // Handle Statistics if (this.settings.collectStats) { - this.statsManager = new StatsManager(this.app.vault, this.app.workspace); + this.statsManager = new StatsManager(this.app.vault, this.app.workspace, this); } // Handle Status Bar diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 466dc5a..73e73d2 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -4,6 +4,7 @@ export enum MetricCounter { sentences, footnotes, citations, + pages, files, } @@ -40,6 +41,7 @@ export interface BetterWordCountSettings { altBar: StatusBarItem[]; countComments: boolean; collectStats: boolean; + pageWords: number; } export const DEFAULT_SETTINGS: BetterWordCountSettings = { @@ -73,4 +75,5 @@ export const DEFAULT_SETTINGS: BetterWordCountSettings = { ], countComments: false, collectStats: false, + pageWords: 300, }; diff --git a/src/settings/SettingsTab.ts b/src/settings/SettingsTab.ts index 0e55833..a6aa43b 100644 --- a/src/settings/SettingsTab.ts +++ b/src/settings/SettingsTab.ts @@ -1,4 +1,4 @@ -import { App, PluginSettingTab, Setting, ToggleComponent } from "obsidian"; +import { App, PluginSettingTab, Setting, ToggleComponent, TextComponent } from "obsidian"; import type BetterWordCount from "src/main"; import { addStatusBarSettings } from "./StatusBarSettings"; @@ -37,6 +37,18 @@ export default class BetterWordCountSettingsTab extends PluginSettingTab { await this.plugin.saveSettings(); }); }); + new Setting(containerEl) + .setName("Page Word Count") + .setDesc("Set how many words count as one \"page\"") + .addText((text: TextComponent) => { + text.inputEl.type = "number"; + text.setPlaceholder("300"); + text.setValue(this.plugin.settings.pageWords.toString()); + text.onChange(async (value: string) => { + this.plugin.settings.pageWords = parseInt(value); + await this.plugin.saveSettings(); + }); + }); // Status Bar Settings addStatusBarSettings(this.plugin, containerEl); diff --git a/src/settings/StatusBarSettings.svelte b/src/settings/StatusBarSettings.svelte index fb506bf..14ece51 100644 --- a/src/settings/StatusBarSettings.svelte +++ b/src/settings/StatusBarSettings.svelte @@ -22,6 +22,8 @@ return "Footnotes in Note" case MetricCounter.citations: return "Citations in Note" + case MetricCounter.pages: + return "Pages in Note" case MetricCounter.files: return "Total Notes" } @@ -37,6 +39,8 @@ return "Daily Footnotes" case MetricCounter.citations: return "Daily Citations" + case MetricCounter.pages: + return "Daily Pages" case MetricCounter.files: return "Total Notes" } @@ -52,6 +56,8 @@ return "Total Footnotes" case MetricCounter.citations: return "Total Citations" + case MetricCounter.pages: + return "Total Pages" case MetricCounter.files: return "Total Notes" } @@ -195,6 +201,7 @@ + @@ -364,6 +371,7 @@ + diff --git a/src/stats/Stats.ts b/src/stats/Stats.ts index f79a973..b613b58 100644 --- a/src/stats/Stats.ts +++ b/src/stats/Stats.ts @@ -9,6 +9,7 @@ export interface Day { words: number; characters: number; sentences: number; + pages: number; files: number; footnotes: number; citations: number; @@ -17,6 +18,7 @@ export interface Day { totalSentences: number; totalFootnotes: number; totalCitations: number; + totalPages: number; } export type ModifiedFiles = Record; @@ -27,6 +29,7 @@ export interface FileStat { words: CountDiff; characters: CountDiff; sentences: CountDiff; + pages: CountDiff; } export interface CountDiff { diff --git a/src/stats/StatsManager.ts b/src/stats/StatsManager.ts index f5fd1f4..e2fee39 100644 --- a/src/stats/StatsManager.ts +++ b/src/stats/StatsManager.ts @@ -1,10 +1,12 @@ import { debounce, Debouncer, TFile, Vault, Workspace } from "obsidian"; +import type BetterWordCount from "../main"; import { STATS_FILE } from "../constants"; import type { Day, VaultStatistics } from "./Stats"; import moment from "moment"; import { getCharacterCount, getSentenceCount, + getPageCount, getWordCount, getCitationCount, getFootnoteCount, @@ -13,13 +15,15 @@ import { export default class StatsManager { private vault: Vault; private workspace: Workspace; + private plugin: BetterWordCount; private vaultStats: VaultStatistics; private today: string; public debounceChange; - constructor(vault: Vault, workspace: Workspace) { + constructor(vault: Vault, workspace: Workspace, plugin: BetterWordCount) { this.vault = vault; this.workspace = workspace; + this.plugin = plugin; this.debounceChange = debounce( (text: string) => this.change(text), 50, @@ -80,11 +84,13 @@ export default class StatsManager { const totalSentences = await this.calcTotalSentences(); const totalFootnotes = await this.calcTotalFootnotes(); const totalCitations = await this.calcTotalCitations(); + const totalPages = await this.calcTotalPages(); const newDay: Day = { words: 0, characters: 0, sentences: 0, + pages: 0, files: 0, footnotes: 0, citations: 0, @@ -93,6 +99,7 @@ export default class StatsManager { totalSentences: totalSentences, totalFootnotes: totalFootnotes, totalCitations: totalCitations, + totalPages: totalPages, }; this.vaultStats.modifiedFiles = {}; @@ -107,6 +114,8 @@ export default class StatsManager { const currentSentences = getSentenceCount(text); const currentCitations = getCitationCount(text); const currentFootnotes = getFootnoteCount(text); + const currentPages = getPageCount(text, this.plugin.settings.pageWords); + if ( this.vaultStats.history.hasOwnProperty(this.today) && this.today === moment().format("YYYY-MM-DD") @@ -124,11 +133,15 @@ export default class StatsManager { currentSentences - modFiles[fileName].footnotes.current; this.vaultStats.history[this.today].totalCitations += currentSentences - modFiles[fileName].citations.current; + this.vaultStats.history[this.today].totalPages += + currentPages - modFiles[fileName].pages.current; + modFiles[fileName].words.current = currentWords; modFiles[fileName].characters.current = currentCharacters; modFiles[fileName].sentences.current = currentSentences; - modFiles[fileName].footnotes.current = currentSentences; - modFiles[fileName].citations.current = currentSentences; + modFiles[fileName].footnotes.current = currentFootnotes; + modFiles[fileName].citations.current = currentCitations; + modFiles[fileName].pages.current = currentPages; } else { modFiles[fileName] = { words: { @@ -150,6 +163,9 @@ export default class StatsManager { citations: { initial: currentCitations, current: currentCitations, + pages: { + initial: currentPages, + current: currentPages, }, }; } @@ -169,6 +185,7 @@ export default class StatsManager { Math.max(0, counts.sentences.current - counts.sentences.initial) ) .reduce((a, b) => a + b, 0); + const footnotes = Object.values(modFiles) .map((counts) => Math.max(0, counts.footnotes.current - counts.footnotes.initial) @@ -178,6 +195,10 @@ export default class StatsManager { .map((counts) => Math.max(0, counts.citations.current - counts.citations.initial) ) + const pages = Object.values(modFiles) + .map((counts) => + Math.max(0, counts.pages.current - counts.pages.initial) + ) .reduce((a, b) => a + b, 0); this.vaultStats.history[this.today].words = words; @@ -185,6 +206,7 @@ export default class StatsManager { this.vaultStats.history[this.today].sentences = sentences; this.vaultStats.history[this.today].footnotes = footnotes; this.vaultStats.history[this.today].citations = citations; + this.vaultStats.history[this.today].pages = pages; this.vaultStats.history[this.today].files = this.getTotalFiles(); await this.update(); @@ -205,6 +227,7 @@ export default class StatsManager { todayHist.totalSentences = await this.calcTotalSentences(); todayHist.totalFootnotes = await this.calcTotalFootnotes(); todayHist.totalCitations = await this.calcTotalCitations(); + todayHist.totalPages = await this.calcTotalPages(); this.update(); } else { this.updateToday(); @@ -248,6 +271,20 @@ export default class StatsManager { } return sentence; } + + private async calcTotalPages(): Promise { + let pages = 0; + + const files = this.vault.getFiles(); + for (const i in files) { + const file = files[i]; + if (file.extension === "md") { + pages += getPageCount(await this.vault.cachedRead(file), this.plugin.settings.pageWords); + } + } + + return pages; + } private async calcTotalFootnotes(): Promise { let footnotes = 0; @@ -285,6 +322,7 @@ export default class StatsManager { return this.vaultStats.history[this.today].sentences; } + public getDailyFootnotes(): number { return this.vaultStats.history[this.today].footnotes; } @@ -292,6 +330,9 @@ export default class StatsManager { public getDailyCitations(): number { return this.vaultStats.history[this.today].citations; } + public getDailyPages(): number { + return this.vaultStats.history[this.today].pages; + } public getTotalFiles(): number { return this.vault.getMarkdownFiles().length; @@ -311,7 +352,7 @@ export default class StatsManager { if (!this.vaultStats) return await this.calcTotalSentences(); return this.vaultStats.history[this.today].totalSentences; } - + public async getTotalFootnotes(): Promise { if (!this.vaultStats) return await this.calcTotalFootnotes(); return this.vaultStats.history[this.today].totalFootnotes; @@ -322,4 +363,8 @@ export default class StatsManager { return this.vaultStats.history[this.today].totalCitations; } + public async getTotalPages(): Promise { + if (!this.vaultStats) return await this.calcTotalPages(); + return this.vaultStats.history[this.today].totalPages; + } } diff --git a/src/status/StatusBar.ts b/src/status/StatusBar.ts index aeee7aa..79c156d 100644 --- a/src/status/StatusBar.ts +++ b/src/status/StatusBar.ts @@ -6,6 +6,7 @@ import { getSentenceCount, getCitationCount, getFootnoteCount, + getPageCount, } from "src/utils/StatUtils"; import { debounce } from "obsidian"; @@ -149,6 +150,26 @@ export default class StatusBar { : 0)); break; } + } else if (metric.counter === MetricCounter.pages) { + switch (metric.type) { + case MetricType.file: + display = display + getPageCount(text, this.plugin.settings.pageWords); + break; + case MetricType.daily: + display = + display + + (this.plugin.settings.collectStats + ? this.plugin.statsManager.getDailyPages() + : 0); + break; + case MetricType.total: + display = + display + + (await (this.plugin.settings.collectStats + ? this.plugin.statsManager.getTotalPages() + : 0)); + break; + } } else if (metric.counter === MetricCounter.files) { switch (metric.type) { case MetricType.file: @@ -252,7 +273,7 @@ export default class StatusBar { break; } } else if (metric.counter === MetricCounter.footnotes) { - switch (metric.type) { + switch (metric.type) { case MetricType.file: display = display + 0; break; @@ -260,14 +281,14 @@ export default class StatusBar { display = display + (this.plugin.settings.collectStats - ? this.plugin.statsManager.getDailyFootnotes() - : 0); + ? this.plugin.statsManager.getDailyFootnotes() + : 0); break; case MetricType.total: display = display + (await (this.plugin.settings.collectStats - ? this.plugin.statsManager.getTotalFootnotes() + ? this.plugin.statsManager.getTotalFootnotes() : 0)); break; } @@ -291,6 +312,26 @@ export default class StatusBar { : 0)); break; } + } else if (metric.counter === MetricCounter.pages) { + switch (metric.type) { + case MetricType.file: + display = display + 0; + break; + case MetricType.daily: + display = + display + + (this.plugin.settings.collectStats + ? this.plugin.statsManager.getDailyPages() + : 0); + break; + case MetricType.total: + display = + display + + (await (this.plugin.settings.collectStats + ? this.plugin.statsManager.getTotalPages() + : 0)); + break; + } } else if (metric.counter === MetricCounter.files) { switch (metric.type) { case MetricType.file: diff --git a/src/utils/StatUtils.ts b/src/utils/StatUtils.ts index 8040486..723f90a 100644 --- a/src/utils/StatUtils.ts +++ b/src/utils/StatUtils.ts @@ -54,6 +54,10 @@ export function getSentenceCount(text: string): number { return sentences; } +export function getPageCount(text: string, pageWords: number): number { + return parseFloat((getWordCount(text) / pageWords).toFixed(1)); +} + export function getTotalFileCount(vault: Vault): number { return vault.getMarkdownFiles().length; }