Add Alt Status Bar

This commit is contained in:
Luke Leppan 2022-12-06 17:12:59 +02:00
parent 67221411f8
commit 5c7ca2abc5
6 changed files with 554 additions and 130 deletions

View file

@ -22,11 +22,11 @@ export default class BetterWordCount extends Plugin {
async onload() {
// Settings Store
this.register(
settingsStore.subscribe((value) => {
this.settings = value;
})
);
// this.register(
// settingsStore.subscribe((value) => {
// this.settings = value;
// })
// );
// Handle Settings
this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData());
this.addSettingTab(new BetterWordCountSettingsTab(this.app, this));
@ -51,14 +51,20 @@ export default class BetterWordCount extends Plugin {
this.app.workspace.on(
"active-leaf-change",
async (leaf: WorkspaceLeaf) => {
await this.statsManager.recalcTotals();
this.giveEditorPlugin(leaf);
if (leaf.view.getViewType() !== "markdown") {
this.statusBar.updateAltBar();
}
if (!this.settings.collectStats) return;
await this.statsManager.recalcTotals();
}
)
);
this.registerEvent(
this.app.vault.on("delete", async () => {
if (!this.settings.collectStats) return;
await this.statsManager.recalcTotals();
})
);

View file

@ -1,44 +1,74 @@
export enum Counter {
fileWords,
fileChars,
fileSentences,
totalWords,
totalChars,
totalSentences,
totalNotes,
export enum MetricCounter {
words,
characters,
sentences,
files,
}
export enum MetricType {
file,
daily,
total,
folder,
}
export interface Metric {
type: MetricType;
counter: MetricCounter;
folder?: string;
}
export interface StatusBarItem {
prefix: string;
suffix: string;
count: Counter;
metric: Metric;
}
export const BLANK_SB_ITEM: StatusBarItem = {
prefix: "",
suffix: "",
count: null,
metric: {
type: null,
counter: null,
},
};
export interface BetterWordCountSettings {
statusBar: StatusBarItem[];
altBar: StatusBarItem[];
countComments: boolean;
collectStats: boolean;
}
export const DEFAULT_SETTINGS: BetterWordCountSettings = Object.freeze({
export const DEFAULT_SETTINGS: BetterWordCountSettings = {
statusBar: [
{
prefix: "",
suffix: " words",
count: Counter.fileWords,
metric: {
type: MetricType.file,
counter: MetricCounter.words,
},
},
{
prefix: " ",
suffix: " characters",
count: Counter.fileChars,
metric: {
type: MetricType.file,
counter: MetricCounter.characters,
},
},
],
altBar: [
{
prefix: "",
suffix: "files",
metric: {
type: MetricType.total,
counter: MetricCounter.files,
},
},
],
countComments: false,
collectStats: false,
});
};

View file

@ -18,7 +18,7 @@ export default class BetterWordCountSettingsTab extends PluginSettingTab {
new Setting(containerEl)
.setName("Collect Statistics")
.setDesc(
"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."
"Reload Required for change to take effect. Turn on to start collecting daily statistics of your writing. Stored in the vault-stats.json file in the .obsidian of your vault. This is required for counts of the day as well as total counts."
)
.addToggle((cb: ToggleComponent) => {
cb.setValue(this.plugin.settings.collectStats);
@ -39,13 +39,6 @@ export default class BetterWordCountSettingsTab extends PluginSettingTab {
});
// Status Bar Settings
containerEl
.createEl("h4", { text: "Status Bar Settings" })
.addClass("bwc-status-bar-settings-title");
containerEl.createEl("p", {
text: "Here you can customize what statistics are displayed on the status bar.",
});
addStatusBarSettings(this.plugin, containerEl);
}
}

View file

@ -1,38 +1,50 @@
<script lang="ts">
import type { StatusBarItem } from "./Settings";
import { Counter, BLANK_SB_ITEM } from "./Settings";
import { MetricType, MetricCounter, BLANK_SB_ITEM, DEFAULT_SETTINGS } from "./Settings";
import type BetterWordCount from "src/main";
export let plugin: BetterWordCount;
const { settings } = plugin;
let statusItems: StatusBarItem[] = [...plugin.settings.statusBar];
let altSItems: StatusBarItem[] = [...plugin.settings.altBar];
function counterToString(count: Counter): string {
switch (count) {
case Counter.fileWords:
return "File Words";
case Counter.fileChars:
return "File Chars";
case Counter.fileSentences:
return "File Sentences";
case Counter.totalWords:
return "Total Words";
case Counter.totalChars:
return "Total Chars";
case Counter.totalSentences:
return "Total Sentences"
case Counter.totalNotes:
return "Total Notes";
default:
return "Select Options"
function metricToString(metric: Metric): string {
if (metric.type === MetricType.file) {
switch (metric.counter) {
case MetricCounter.words:
return "Words in Note"
case MetricCounter.characters:
return "Chars in Note"
case MetricCounter.sentences:
return "Sentences in Note"
case MetricCounter.files:
return "Total Notes"
}
} else if (metric.type === MetricType.daily) {
switch (metric.counter) {
case MetricCounter.words:
return "Daily Words"
case MetricCounter.characters:
return "Daily Chars"
case MetricCounter.sentences:
return "Daily Sentences"
case MetricCounter.files:
return "Total Notes"
}
} else if (metric.type === MetricType.total) {
switch (metric.counter) {
case MetricCounter.words:
return "Total Words"
case MetricCounter.characters:
return "Total Chars"
case MetricCounter.sentences:
return "Total Sentences"
case MetricCounter.files:
return "Total Notes"
}
} else {
return "Select Options"
}
}
@ -47,19 +59,32 @@
async function update(statusItems: StatusBarItem[]) {
plugin.settings.statusBar = statusItems.filter((item) => {
if (counterToString(item.count) !== "Select Options") {
if (metricToString(item.metric) !== "Select Options") {
return item;
}
});
await plugin.saveSettings();
}
async function updateAlt(altSItems: StatusBarItem[]) {
plugin.settings.altBar = altSItems.filter((item) => {
if (metricToString(item.metric) !== "Select Options") {
return item;
}
});
await plugin.saveSettings();
}
</script>
<div>
<h4>Markdown Status Bar</h4>
<p>Here you can customize what statistics are displayed on the status bar when editing a markdown note.</p>
<div class="bwc-sb-buttons">
<button
aria-label="Add New Status Bar Item"
on:click={async () => (statusItems = [...statusItems, Object.create(BLANK_SB_ITEM)])}
on:click={async () => (statusItems = [...statusItems, JSON.parse(JSON.stringify(BLANK_SB_ITEM))])}
>
<div class="icon">
Add Item
@ -68,19 +93,26 @@
<button
aria-label="Reset Status Bar to Default"
on:click={async () => {
statusItems = [{
prefix: "",
suffix: " words",
count: Counter.fileWords,
},
{
prefix: " ",
suffix: " characters",
count: Counter.fileChars,
},
];
await update(statusItems);
}}
statusItems = [
{
prefix: "",
suffix: " words",
metric: {
type: MetricType.file,
counter: MetricCounter.words,
},
},
{
prefix: " ",
suffix: " characters",
metric: {
type: MetricType.file,
counter: MetricCounter.characters,
},
},
];
await update(statusItems);
}}
>
<div class="icon">
Reset
@ -91,7 +123,7 @@
<details class="bwc-sb-item-setting">
<summary>
<span class="bwc-sb-item-text">
{counterToString(item.count)}
{metricToString(item.metric)}
</span>
<span class="bwc-sb-buttons">
{#if i !== 0}
@ -129,39 +161,52 @@
</summary>
<div class="setting-item">
<div class="setting-item-info">
<div class="setting-item-name">Count Type</div>
<div class="setting-item-name">Metric Counter</div>
<div class="setting-item-description">
This is the type of counter that will be displayed.
Select the counter to display, e.g. words, characters.
</div>
</div>
<div class="setting-item-control">
<select
class="dropdown"
value={item.count}
value={item.metric.counter}
on:change={async (e) => {
const {value} = e.target;
item.count = Counter[Counter[value]];
item.metric.counter = MetricCounter[MetricCounter[value]];
await update(statusItems);
await plugin.saveSettings();
}}
>
<option value>Select Option</option>
<optgroup label="Words">
<option value={Counter.fileWords}>{counterToString(Counter.fileWords)}</option>
<option value={Counter.totalWords}>{counterToString(Counter.totalWords)}</option>
</optgroup>
<optgroup label="Characters">
<option value={Counter.fileChars}>{counterToString(Counter.fileChars)}</option>
<option value={Counter.totalChars}>{counterToString(Counter.totalChars)}</option>
</optgroup>
<optgroup label="Sentences">
<option value={Counter.fileSentences}>{counterToString(Counter.fileSentences)}</option>
<option value={Counter.totalSentences}>{counterToString(Counter.totalSentences)}</option>
</optgroup>
<optgroup label="Notes">
<option value={Counter.totalNotes}>{counterToString(Counter.totalNotes)}</option>
</optgroup>
<option value={MetricCounter.words}>Words</option>
<option value={MetricCounter.characters}>Characters</option>
<option value={MetricCounter.sentences}>Sentences</option>
<option value={MetricCounter.files}>Files</option>
</select>
</div>
</div>
<div class="setting-item">
<div class="setting-item-info">
<div class="setting-item-name">Metric Type</div>
<div class="setting-item-description">
Select the type of metric that you want displayed.
</div>
</div>
<div class="setting-item-control">
<select
class="dropdown"
value={item.metric.type}
on:change={async (e) => {
const {value} = e.target;
item.metric.type = MetricType[MetricType[value]];
await update(statusItems);
await plugin.saveSettings();
}}
>
<option value>Select Option</option>
<option value={MetricType.file}>Current Note</option>
<option value={MetricType.daily}>Daily Metric</option>
<option value={MetricType.total}>Total in Vault</option>
</select>
</div>
</div>
@ -209,4 +254,171 @@
</div>
</details>
{/each}
<h4>Alternative Status Bar</h4>
<p>Here you can customize what statistics are displayed on the status bar when not editing a markdown file.</p>
<div class="bwc-sb-buttons">
<button
aria-label="Add New Status Bar Item"
on:click={async () => (altSItems = [...altSItems, JSON.parse(JSON.stringify(BLANK_SB_ITEM))])}
>
<div class="icon">
Add Item
</div>
</button>
<button
aria-label="Reset Status Bar to Default"
on:click={async () => {
altSItems = [
{
prefix: "",
suffix: " files",
metric: {
type: MetricType.total,
counter: MetricCounter.files,
},
},
];
await update(statusItems);
}}
>
<div class="icon">
Reset
</div>
</button>
</div>
{#each altSItems as item, i}
<details class="bwc-sb-item-setting">
<summary>
<span class="bwc-sb-item-text">
{metricToString(item.metric)}
</span>
<span class="bwc-sb-buttons">
{#if i !== 0}
<button
aria-label="Move Status Bar Item Up"
on:click={async () => {
altSItems = swapStatusBarItems(i, i-1, altSItems);
await updateAlt(altSItems);
}}
>
</button>
{/if}
{#if i !== altSItems.length - 1}
<button
aria-label="Move Status Bar Item Down"
on:click={async () => {
altSItems = swapStatusBarItems(i, i+1, altSItems);
await updateAlt(altSItems);
}}
>
</button>
{/if}
<button
aria-label="Remove Status Bar Item"
on:click={async () => {
altSItems = altSItems.filter((item, j) => i !== j);
await updateAlt(altSItems);
}}
>
X
</button>
</span>
</summary>
<div class="setting-item">
<div class="setting-item-info">
<div class="setting-item-name">Metric Counter</div>
<div class="setting-item-description">
Select the counter to display, e.g. words, characters.
</div>
</div>
<div class="setting-item-control">
<select
class="dropdown"
value={item.metric.counter}
on:change={async (e) => {
const {value} = e.target;
item.metric.counter = MetricCounter[MetricCounter[value]];
await updateAlt(altSItems);
await plugin.saveSettings();
}}
>
<option value>Select Option</option>
<option value={MetricCounter.words}>Words</option>
<option value={MetricCounter.characters}>Characters</option>
<option value={MetricCounter.sentences}>Sentences</option>
<option value={MetricCounter.files}>Files</option>
</select>
</div>
</div>
<div class="setting-item">
<div class="setting-item-info">
<div class="setting-item-name">Metric Type</div>
<div class="setting-item-description">
Select the type of metric that you want displayed.
</div>
</div>
<div class="setting-item-control">
<select
class="dropdown"
value={item.metric.type}
on:change={async (e) => {
const {value} = e.target;
item.metric.type = MetricType[MetricType[value]];
await updateAlt(altSItems);
await plugin.saveSettings();
}}
>
<option value>Select Option</option>
<option value={MetricType.file}>Current Note</option>
<option value={MetricType.daily}>Daily Metric</option>
<option value={MetricType.total}>Total in Vault</option>
</select>
</div>
</div>
<div class="setting-item">
<div class="setting-item-info">
<div class="setting-item-name">Prefix Text</div>
<div class="setting-item-description">
This is the text that is placed before the count.
</div>
</div>
<div class="setting-item-control">
<input
type="text"
name="prefix"
value={item.prefix}
on:change={async (e) => {
const { value } = e.target;
item.prefix = value;
await updateAlt(altSItems);
await plugin.saveSettings();
}}
/>
</div>
</div>
<div class="setting-item">
<div class="setting-item-info">
<div class="setting-item-name">Suffix Text</div>
<div class="setting-item-description">
This is the text that is placed after the count.
</div>
</div>
<div class="setting-item-control">
<input
type="text"
name="suffix"
value={item.suffix}
on:change={async (e) => {
const { value } = e.target;
item.suffix = value;
await updateAlt(altSItems);
await plugin.saveSettings();
}}
/>
</div>
</div>
</details>
{/each}
</div>

View file

@ -1,9 +1,6 @@
import { debounce, Debouncer, TFile, Vault, Workspace } from "obsidian";
import { STATS_FILE } from "../constants";
import type {
Day,
VaultStatistics,
} from "./Stats";
import type { Day, VaultStatistics } from "./Stats";
import moment from "moment";
import {
getCharacterCount,
@ -21,7 +18,11 @@ export default class StatsManager {
constructor(vault: Vault, workspace: Workspace) {
this.vault = vault;
this.workspace = workspace;
this.debounceChange = debounce((text:string) => this.change(text), 50, false)
this.debounceChange = debounce(
(text: string) => this.change(text),
50,
false
);
this.vault.adapter.exists(STATS_FILE).then(async (exists) => {
if (!exists) {
@ -33,6 +34,14 @@ export default class StatsManager {
this.vaultStats = JSON.parse(await this.vault.adapter.read(STATS_FILE));
} else {
this.vaultStats = JSON.parse(await this.vault.adapter.read(STATS_FILE));
if (!this.vaultStats.hasOwnProperty("history")) {
const vaultSt: VaultStatistics = {
history: {},
modifiedFiles: {},
};
await this.vault.adapter.write(STATS_FILE, JSON.stringify(vaultSt));
}
this.vaultStats = JSON.parse(await this.vault.adapter.read(STATS_FILE));
}
await this.updateToday();
@ -81,14 +90,15 @@ export default class StatsManager {
let modFiles = this.vaultStats.modifiedFiles;
if (modFiles.hasOwnProperty(fileName)) {
this.vaultStats.history[this.today].totalWords += currentWords - modFiles[fileName].words.current;
this.vaultStats.history[this.today].totalCharacters += currentCharacters - modFiles[fileName].characters.current;
this.vaultStats.history[this.today].totalSentences += currentSentences - modFiles[fileName].sentences.current;
this.vaultStats.history[this.today].totalWords +=
currentWords - modFiles[fileName].words.current;
this.vaultStats.history[this.today].totalCharacters +=
currentCharacters - modFiles[fileName].characters.current;
this.vaultStats.history[this.today].totalSentences +=
currentSentences - modFiles[fileName].sentences.current;
modFiles[fileName].words.current = currentWords;
modFiles[fileName].characters.current = currentCharacters;
modFiles[fileName].sentences.current = currentSentences;
} else {
modFiles[fileName] = {
words: {
@ -102,7 +112,7 @@ export default class StatsManager {
sentences: {
initial: currentSentences,
current: currentSentences,
}
},
};
}
@ -141,8 +151,8 @@ export default class StatsManager {
) {
const todayHist: Day = this.vaultStats.history[this.today];
todayHist.totalWords = await this.calcTotalWords();
todayHist.totalCharacters = await this.calcTotalCharacters()
todayHist.totalSentences = await this.calcTotalSentences()
todayHist.totalCharacters = await this.calcTotalCharacters();
todayHist.totalSentences = await this.calcTotalSentences();
this.update();
} else {
this.updateToday();
@ -188,6 +198,18 @@ export default class StatsManager {
return sentence;
}
public getDailyWords(): number {
return this.vaultStats.history[this.today].words;
}
public getDailyCharacters(): number {
return this.vaultStats.history[this.today].characters;
}
public getDailySentences(): number {
return this.vaultStats.history[this.today].sentences;
}
public getTotalFiles(): number {
return this.vault.getMarkdownFiles().length;
}

View file

@ -1,4 +1,4 @@
import { Counter } from "src/settings/Settings";
import { MetricCounter, MetricType } from "src/settings/Settings";
import type BetterWordCount from "../main";
import {
getWordCount,
@ -45,33 +45,194 @@ export default class StatusBar {
const sbItem = sb[i];
display = display + sbItem.prefix;
switch (sbItem.count) {
case Counter.fileWords:
display = display + getWordCount(text);
break;
case Counter.fileChars:
display = display + getCharacterCount(text);
break;
case Counter.fileSentences:
display = display + getSentenceCount(text);
break;
case Counter.totalWords:
display = display + (await this.plugin.statsManager.getTotalWords());
break;
case Counter.totalChars:
display =
display + (await this.plugin.statsManager.getTotalCharacters());
break;
case Counter.totalSentences:
display =
display + (await this.plugin.statsManager.getTotalSentences());
break;
case Counter.totalNotes:
display = display + this.plugin.statsManager.getTotalFiles();
break;
const metric = sbItem.metric;
default:
break;
if (metric.counter === MetricCounter.words) {
switch (metric.type) {
case MetricType.file:
display = display + getWordCount(text);
break;
case MetricType.daily:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getDailyWords()
: 0);
break;
case MetricType.total:
display =
display +
(await (this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalWords()
: 0));
break;
}
} else if (metric.counter === MetricCounter.characters) {
switch (metric.type) {
case MetricType.file:
display = display + getCharacterCount(text);
break;
case MetricType.daily:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getDailyCharacters()
: 0);
break;
case MetricType.total:
display =
display +
(await (this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalCharacters()
: 0));
break;
}
} else if (metric.counter === MetricCounter.sentences) {
switch (metric.type) {
case MetricType.file:
display = display + getSentenceCount(text);
break;
case MetricType.daily:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getDailySentences()
: 0);
break;
case MetricType.total:
display =
display +
(await (this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalSentences()
: 0));
break;
}
} else if (metric.counter === MetricCounter.files) {
switch (metric.type) {
case MetricType.file:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalFiles()
: 0);
break;
case MetricType.daily:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalFiles()
: 0);
break;
case MetricType.total:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalFiles()
: 0);
break;
}
}
display = display + sbItem.suffix;
}
this.displayText(display);
}
async updateAltBar() {
const ab = this.plugin.settings.altBar;
let display = "";
for (let i = 0; i < ab.length; i++) {
const sbItem = ab[i];
display = display + sbItem.prefix;
const metric = sbItem.metric;
if (metric.counter === MetricCounter.words) {
switch (metric.type) {
case MetricType.file:
display = display + 0;
break;
case MetricType.daily:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getDailyWords()
: 0);
break;
case MetricType.total:
display =
display +
(await (this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalWords()
: 0));
break;
}
} else if (metric.counter === MetricCounter.characters) {
switch (metric.type) {
case MetricType.file:
display = display + 0;
break;
case MetricType.daily:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getDailyCharacters()
: 0);
break;
case MetricType.total:
display =
display +
(await (this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalCharacters()
: 0));
break;
}
} else if (metric.counter === MetricCounter.sentences) {
switch (metric.type) {
case MetricType.file:
display = display + 0;
break;
case MetricType.daily:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getDailySentences()
: 0);
break;
case MetricType.total:
display =
display +
(await (this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalSentences()
: 0));
break;
}
} else if (metric.counter === MetricCounter.files) {
switch (metric.type) {
case MetricType.file:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalFiles()
: 0);
break;
case MetricType.daily:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalFiles()
: 0);
break;
case MetricType.total:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalFiles()
: 0);
break;
}
}
display = display + sbItem.suffix;