Skip to content

Commit 5212a2a

Browse files
committed
Added an option to count all words in a folder
1 parent 3f7ec65 commit 5212a2a

File tree

6 files changed

+172
-0
lines changed

6 files changed

+172
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"typescript": "^4.0.3"
4242
},
4343
"dependencies": {
44+
"chart.js": "^4.3.0",
4445
"svelte": "^3.38.3",
4546
"svelte-icons": "^2.1.0"
4647
}

src/main.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
DEFAULT_SETTINGS,
1010
} from "src/settings/Settings";
1111
import { settingsStore } from "./utils/SvelteStores";
12+
import { handleFileMenu } from "./utils/FileMenu";
1213

1314
export default class BetterWordCount extends Plugin {
1415
public settings: BetterWordCountSettings;
@@ -68,6 +69,13 @@ export default class BetterWordCount extends Plugin {
6869
await this.statsManager.recalcTotals();
6970
})
7071
);
72+
73+
// Register a new action for right clicking on folders
74+
this.registerEvent(
75+
this.app.workspace.on("file-menu", (menu, file, source) => {
76+
handleFileMenu(menu, file, source, this);
77+
})
78+
);
7179
}
7280

7381
giveEditorPlugin(leaf: WorkspaceLeaf): void {

src/utils/FileMenu.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Menu, TAbstractFile, TFile } from "obsidian";
2+
import type BetterWordCount from "src/main";
3+
import { FolderStatisticsModal } from "src/view/FolderStatisticsModal";
4+
5+
export function handleFileMenu(menu: Menu, file: TAbstractFile, source: string, plugin: BetterWordCount): void {
6+
if (source !== "file-explorer-context-menu") {
7+
return;
8+
}
9+
if (!file) {
10+
return;
11+
}
12+
// Make sure the menu only shows up for folders
13+
if (file instanceof TFile) {
14+
return;
15+
}
16+
menu.addItem((item) => {
17+
item.setTitle(`Count Words`)
18+
.setIcon("info")
19+
.setSection("action")
20+
.onClick(async (_) => {
21+
new FolderStatisticsModal(plugin, file).open();
22+
});
23+
});
24+
}

src/utils/FileUtils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { TFile } from "obsidian";
2+
import type BetterWordCount from "src/main";
3+
4+
// get all Markdown files in a folder and all subfolders
5+
export function getAllFilesInFolder(
6+
plugin: BetterWordCount,
7+
path: string
8+
): TFile[] {
9+
// get all files and filter them by the start of the path
10+
return plugin.app.vault
11+
.getMarkdownFiles()
12+
.filter((tFolder) => tFolder.path.startsWith(path));
13+
}
14+
15+
// Function to convert a list of Files to a list of file contents
16+
export async function getAllFileContentInFolder(files: TFile[]): Promise<string[]> {
17+
// Create a promise to read the file content for each file
18+
const readPromise = files.map((file) => {
19+
return file.vault.cachedRead(file);
20+
});
21+
// resolve all promises and return the array
22+
return (await Promise.all(readPromise));
23+
}

src/view/FolderStatistics.svelte

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<script lang="ts">
2+
import type { TAbstractFile } from "obsidian";
3+
import type BetterWordCount from "src/main";
4+
import {
5+
getAllFileContentInFolder,
6+
getAllFilesInFolder,
7+
} from "src/utils/FileUtils";
8+
import { ArcElement, Chart, PieController, Tooltip } from "chart.js";
9+
import { getWordCount } from "src/utils/StatUtils";
10+
// Enable minimal chart js
11+
Chart.register(ArcElement, PieController);
12+
Chart.register(Tooltip);
13+
14+
export let file: TAbstractFile;
15+
export let plugin: BetterWordCount;
16+
17+
let chartContainer: HTMLCanvasElement;
18+
19+
// Function to map the word count of each file to a chart js data object
20+
const getFolderWordStats = async () => {
21+
// Get all files in the folder
22+
const allFiles = getAllFilesInFolder(plugin, file.path);
23+
24+
// Get the content of all files in the folder
25+
const content = await getAllFileContentInFolder(allFiles);
26+
27+
// Get the word count of all files in the folder
28+
const wordCounts = content.map((c) => getWordCount(c));
29+
30+
return {
31+
labels: allFiles.map((file) => file.name),
32+
datasets: [
33+
{
34+
label: "Word Count",
35+
data: wordCounts,
36+
backgroundColor: "rgba(255, 99, 132, 0.2)",
37+
borderColor: "rgba(255, 99, 132, 1)",
38+
borderWidth: 1,
39+
},
40+
],
41+
};
42+
};
43+
44+
// Function to get all the data and render the chart
45+
async function renderChart(): Promise<number> {
46+
const options = {
47+
title: {
48+
display: true,
49+
text: "All Files and there Word Count",
50+
position: "top",
51+
},
52+
rotation: -0.7 * Math.PI,
53+
legend: {
54+
display: false,
55+
},
56+
};
57+
const data = await getFolderWordStats();
58+
59+
new Chart(chartContainer, {
60+
type: "pie",
61+
data: data,
62+
options: options,
63+
});
64+
65+
return data.datasets[0].data.reduce(
66+
(acc, current) => (acc += current),
67+
0
68+
);
69+
}
70+
</script>
71+
72+
<div>
73+
<h1>{file.name}</h1>
74+
75+
{#await renderChart()}
76+
<p>Counting</p>
77+
{:then data}
78+
<p>Total: {data} words</p>
79+
{:catch error}
80+
<p style="color: red">{error.message}</p>
81+
{/await}
82+
83+
<canvas class="pieChart" bind:this={chartContainer} />
84+
</div>

src/view/FolderStatisticsModal.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Modal, TAbstractFile } from "obsidian";
2+
import type BetterWordCount from "src/main";
3+
//@ts-ignore
4+
import FolderStatistics from "./FolderStatistics.svelte";
5+
6+
// Modal to wrap the svelte component passing the required props
7+
export class FolderStatisticsModal extends Modal {
8+
9+
file: TAbstractFile;
10+
plugin: BetterWordCount;
11+
12+
constructor(plugin: BetterWordCount, file: TAbstractFile) {
13+
super(plugin.app);
14+
this.plugin = plugin;
15+
this.file = file;
16+
}
17+
18+
async onOpen(): Promise<void> {
19+
const { contentEl } = this;
20+
new FolderStatistics({
21+
target: contentEl,
22+
props: {
23+
plugin: this.plugin,
24+
file: this.file,
25+
},
26+
});
27+
}
28+
onClose(): void {
29+
const { contentEl } = this;
30+
contentEl.empty();
31+
}
32+
}

0 commit comments

Comments
 (0)