Compare commits

...

132 commits

Author SHA1 Message Date
Luke Leppan
3f7ec6561e
bump: version 0.9.6 2023-03-30 01:15:51 +02:00
Luke Leppan
8e7860d510
add: should be able to remove those comments 2023-03-30 01:14:00 +02:00
Luke Leppan
141c39dbb7
bump: version 0.9.5 2023-03-30 00:58:33 +02:00
Luke Leppan
0a208ea8ef
Merge pull request #79 from chrisgrieser/master
Feat: Option to add Citations / Footnote Counts
2023-03-30 00:52:31 +02:00
Luke Leppan
d2f99e548a
fix: issue from merge conflict 2023-03-30 00:50:06 +02:00
Luke Leppan
66c00b24c2
Merge branch 'master' into master 2023-03-30 00:38:07 +02:00
Luke Leppan
437547f02e
Merge pull request #76 from minermaniac447/master 2023-03-30 00:03:09 +02:00
Luke Leppan
0712d9f61a
fix: calculating with currentSentences instead of page 2023-03-30 00:01:11 +02:00
Luke Leppan
d3b6938e48
Merge pull request #74 from bakuzan/master
Allow better word count to correctly reflect selection count when using shift+home
2023-03-29 23:36:11 +02:00
Luke Leppan
b2a43d9ca3
update readme 2023-03-28 21:20:01 +02:00
Luke Leppan
9bbeeff386
update readme 2023-03-28 21:06:34 +02:00
pseudometa
4a7ed4730c fix: citation and footnote actually displaying 2023-02-17 13:56:18 +01:00
pseudometa
3412745204 fix: settings for footnotes and citations 2023-02-17 11:52:47 +01:00
pseudometa
fee3421508 docs: fix typos 2023-02-17 11:41:36 +01:00
pseudometa
b2ef959464 feat: add footnote & pandoc citation count 2023-02-17 11:40:51 +01:00
minermaniac447
86ca1199cc
Fix TS Build Mistakes 2023-02-08 18:25:43 -05:00
minermaniac447
d865d61e23
Fix TS Build Mistakes 2023-02-08 18:16:09 -05:00
minermaniac447
ec21413d55
Fix TS Build Mistakes 2023-02-08 18:15:04 -05:00
minermaniac447
fbf7977188
Fix TS Build Mistakes 2023-02-08 17:31:07 -05:00
minermaniac447
2b847ddcc9
Fix TS Build Mistakes 2023-02-08 17:18:49 -05:00
minermaniac447
aabd175d5d
Fix TS Build Mistakes 2023-02-08 17:18:06 -05:00
minermaniac447
f43895645f
Update StatsManager Initialization 2023-02-08 16:45:38 -05:00
minermaniac447
5e970cad74
Add getPageCount 2023-02-08 16:44:23 -05:00
minermaniac447
de3172ee95
Add Page Display Options 2023-02-08 16:42:00 -05:00
minermaniac447
759b4d949c
Add Page Calculations 2023-02-08 16:39:28 -05:00
minermaniac447
9885035ac8
Add Pages 2023-02-08 16:12:36 -05:00
minermaniac447
2cc592c13e
Add "Pages" as an Option 2023-02-08 16:11:23 -05:00
minermaniac447
88a3bd1a2a
Add Page Word Count Setting 2023-02-08 16:03:24 -05:00
minermaniac447
547fb32f95
Add Page-Related Settings 2023-02-08 15:59:10 -05:00
bakuzan
9cdab1d1d7 Remove log 2023-01-30 11:14:14 +00:00
bakuzan
44dfcb9fe3 Work around for undefined user event type. Fixes #69. 2023-01-30 11:12:39 +00:00
Luke Leppan
566f8531b5
bump 0.9.4 2023-01-27 10:27:04 +02:00
Luke Leppan
496d708117
Merge pull request #72 from Noxellar/patch-1 2023-01-27 10:24:06 +02:00
Noxellar
3871f71aae
Adding a space to the default suffix for the alt status bar 2023-01-27 19:20:17 +11:00
Luke Leppan
909e954c3e
bump 2023-01-25 13:48:52 +02:00
Luke Leppan
76ac1fbc86
Merge pull request #71 from THeK3nger/master
Delete history for deleted files
2023-01-25 13:47:42 +02:00
Davide Aversa
da44785003 delete history for deleted files
Similar to the "rename" case, I added an event handler to clean up the `vault-stats.json` when a file is deleted.

Moreover, I added a sanity check on the "rename" event handler.
2023-01-25 10:03:48 +01:00
Luke Leppan
6676fc69f4
Merge branch 'master' of github.com:lukeleppan/better-word-count 2023-01-24 21:15:38 +02:00
Luke Leppan
113c9ed172
Merge branch 'THeK3nger-master' 2023-01-24 21:14:21 +02:00
Luke Leppan
0ae40119e7
bump 2023-01-24 21:13:27 +02:00
Luke Leppan
07cdeffa31
Merge pull request #68 from bakuzan/master
Protect against leaf not existing when obsidian first starts up.
2023-01-24 21:02:16 +02:00
Davide Aversa
67c8162296 add listerner for renamed file
This change avoid to create duplicate data in `vault-stats.json` when a
file is renamed by copying the data from the old path to the new one.
2023-01-24 19:53:26 +01:00
bakuzan
29766d2fe6
Add null protection.
I was getting "extension could not load error" on starting up obsidian.
This was because of leaf not being its expected value.
2023-01-21 09:02:05 +00:00
Luke Leppan
9e6550f407 bump 2022-12-07 02:13:42 +02:00
Luke Leppan
4e1b952c1e Merge branch 'turnage-patch-1' 2022-12-07 02:12:17 +02:00
Luke Leppan
f807c531e9 Merge branch 'patch-1' of github.com:turnage/better-word-count into turnage-patch-1 2022-12-07 02:06:52 +02:00
Luke Leppan
8b253b71eb Fix workflow 2022-12-06 21:58:42 +02:00
Luke Leppan
8a522c9cdd Clean up 2022-12-06 21:40:04 +02:00
Luke Leppan
5c7ca2abc5 Add Alt Status Bar 2022-12-06 17:12:59 +02:00
Luke Leppan
67221411f8
Rewrite Mostly Stable 2022-11-16 11:48:56 +02:00
Luke Leppan
88eea5ad67
Fixes 2022-11-15 22:39:34 +02:00
Luke Leppan
8b7630a357
Working State 2022-11-15 19:21:08 +02:00
Luke Leppan
c33160dff5
Editing... 2022-11-10 17:23:23 +02:00
Luke Leppan
1ae2418394
Finish Settings 2022-11-08 06:45:18 +02:00
Luke Leppan
3192b681b8
Merge 2022-11-06 21:13:47 +02:00
Luke Leppan
fa94b19660
begining 2022-11-06 18:27:06 +02:00
Luke Leppan
56fe1653be Merge branch 'hotfix/0.8.1' into develop 2022-02-24 15:31:11 +02:00
Luke Leppan
ce45997d64 Merge branch 'hotfix/0.8.1' 2022-02-24 15:31:11 +02:00
Luke Leppan
8226f4a7cb Fix subtle bug 2022-02-24 15:31:02 +02:00
Luke Leppan
522898f148 Merge branch 'release/0.8.0' 2022-02-24 15:09:56 +02:00
Luke Leppan
a16551950c Merge branch 'master' of https://github.com/lukeleppan/better-word-count 2022-02-24 15:06:26 +02:00
Luke Leppan
320bf4dfa6 Merge branch 'hotfix/0.7.8' into develop 2022-02-24 15:03:31 +02:00
Luke Leppan
34ca84bb4d Merge branch 'hotfix/0.7.8' 2022-02-24 15:03:31 +02:00
Luke Leppan
219ddaebf7 bump 2022-02-24 15:03:21 +02:00
Luke Leppan
3434a4f145 Fix #35 #32 #33 2022-02-24 15:02:26 +02:00
Luke Leppan
3a063e3368
Merge pull request #41 from simondoesstuff/patch-1
Included warning about the legacy editor.
2022-02-20 16:57:55 +02:00
Simon Walker
98ba29b592
Included warning about the legacy editor.
The latest live-preview editor breaks the plugin. Added a warning for users in the README that they may nee to switch to the legacy editor until support for the new editor is implemented.
2022-02-12 16:44:25 -07:00
Payton Turnage
3e3656c7d9
Recognize apostrophes as being part of a word. 2021-12-24 10:06:24 -08:00
Luke Leppan
66544e21d1 Merge branch 'hotfix/0.7.7' into develop 2021-07-17 16:17:47 +02:00
Luke Leppan
f6fd30c385 Merge branch 'hotfix/0.7.7' 2021-07-17 16:17:46 +02:00
Luke Leppan
847c0d0bfd 📝 Update README 2021-07-17 16:17:36 +02:00
Luke Leppan
7a78e9e3b7 🔖 bump 0.7.7 2021-07-17 16:13:05 +02:00
Luke Leppan
ca577458d8 🐛 Fix #14 and #16 and probably #15 2021-07-17 16:11:05 +02:00
Luke Leppan
75f08ed1b3 Merge branch 'hotfix/0.7.6' into develop 2021-07-12 14:53:24 +02:00
Luke Leppan
ac7a6b5984 Merge branch 'hotfix/0.7.6' 2021-07-12 14:53:22 +02:00
Luke Leppan
6d4d35af53 🔖 bump 0.7.6 2021-07-12 14:53:05 +02:00
Luke Leppan
3fef6659e3 Merge branch 'hotfix/0.7.5' into develop 2021-07-12 14:49:17 +02:00
Luke Leppan
25ff6c9be3 Merge branch 'hotfix/0.7.5' 2021-07-12 14:49:14 +02:00
Luke Leppan
9a6df0ea9b 🔖 bump 0.7.5 2021-07-12 14:49:03 +02:00
Luke Leppan
89c74b9054 🐛 Fix #15 2021-07-12 14:47:37 +02:00
Luke Leppan
e7160bbee9 Merge branch 'hotfix/0.7.4' into develop 2021-07-12 12:12:48 +02:00
Luke Leppan
f17d71342a Merge branch 'hotfix/0.7.4' 2021-07-12 12:12:45 +02:00
Luke Leppan
9572201b4e 🔖 bump 0.7.3 2021-07-12 12:12:29 +02:00
Luke Leppan
ddaeb41d4d 🐛 Fix incorrect defaults #15 2021-07-12 12:10:54 +02:00
Luke Leppan
6c7e97e21e Merge branch 'hotfix/0.7.3' into develop 2021-07-11 23:37:51 +02:00
Luke Leppan
8937d6ea31 Merge branch 'hotfix/0.7.3' 2021-07-11 23:37:49 +02:00
Luke Leppan
57716f8804 🔖 bump 0.7.3 2021-07-11 23:37:32 +02:00
Luke Leppan
90a277bc53 improves High CPU usage #14 2021-07-11 23:36:48 +02:00
Luke Leppan
34e88bea08 Merge branch 'hotfix/0.7.2' into develop 2021-07-11 13:20:25 +02:00
Luke Leppan
421a0599fa Merge branch 'hotfix/0.7.2' 2021-07-11 13:20:24 +02:00
Luke Leppan
bc77ae4993 bump 2021-07-11 13:20:11 +02:00
Luke Leppan
5ab0b76dd9 Merge branch 'hotfix/0.7.1' into develop 2021-07-11 13:19:09 +02:00
Luke Leppan
483889ce67 Merge branch 'hotfix/0.7.1' 2021-07-11 13:19:08 +02:00
Luke Leppan
03989d6b7a 🐛 Fix Broken Stats 2021-07-11 13:18:37 +02:00
Luke Leppan
992ff69040 🔀 Merge some stuff 2021-07-10 20:43:45 +02:00
Luke Leppan
72d8ac4a24 . 2021-07-10 05:37:54 +02:00
Luke Leppan
bef425e8ec Merge branch 'release/0.7.0' into develop 2021-07-08 22:58:00 +02:00
Luke Leppan
664cd4e4ed Merge branch 'release/0.7.0' 2021-07-08 22:58:00 +02:00
Luke Leppan
cc7eb1ad5d bump 2021-07-08 22:57:29 +02:00
Luke Leppan
f9e3f467a1 fix syntax 2021-07-08 22:54:59 +02:00
Luke Leppan
f1440baa4d add stats syntax 2021-07-08 22:42:07 +02:00
Luke Leppan
5331623a5a Merge branch 'feature/reword-structure' into develop 2021-07-08 22:23:27 +02:00
Luke Leppan
5aa51986fe Add Daily Counts #11 2021-07-08 22:20:38 +02:00
Luke Leppan
7f817bd5bd add #13 2021-07-07 19:29:16 +02:00
Luke Leppan
691ffe88ea total counts working 2021-07-07 19:00:11 +02:00
Luke Leppan
7c420b0b0b getting closer 2021-07-07 14:34:39 +02:00
Luke Leppan
49ad4d12f5 turning out to be a much larger update 😂 2021-07-03 21:22:50 +02:00
Luke Leppan
760b2211b3 Merge branch 'release/0.6.2' 2021-06-20 09:30:11 +02:00
Luke Leppan
f5a4f7f2e0 Merge branch 'release/0.6.2' into develop 2021-06-20 09:30:11 +02:00
Luke Leppan
1313e73a6f edit README 2021-06-20 09:29:58 +02:00
Luke Leppan
83e7549191 bump 2021-06-20 09:28:24 +02:00
Luke Leppan
a2843911ba Edit Stats.ts
Should fix #12
2021-06-20 09:04:16 +02:00
Luke Leppan
676ef31827 Merge branch 'develop' 2021-04-03 12:09:28 +02:00
Luke Leppan
dc1a4496c4 Merge branch 'hotfix/0.6.1' into develop 2021-04-03 11:58:41 +02:00
Luke Leppan
c7ebc4945e Merge branch 'hotfix/0.6.1' 2021-04-03 11:58:41 +02:00
Luke Leppan
bb56cc57a6 bump 2021-04-03 11:57:41 +02:00
Luke Leppan
7f1361960f Edit README.md 2021-04-03 11:56:14 +02:00
Luke Leppan
7ca78e53ea Merge branch 'master' into develop 2021-04-03 11:49:28 +02:00
Luke Leppan
05a85a472e
Merge pull request #9 from leoccyao/total-word-count
Add total word counts into graph view count
2021-04-03 11:48:44 +02:00
Leo Yao
4b60c3ee86 Add total word counts into graph view count
This patch adds total word/character/sentence counts into the count
for non-markdown files. Visibility of each count is based on already
existing settings.

Note that the first call of updateAltCount() has been moved after
the settings are loaded.
2021-04-02 17:24:17 -04:00
Luke Leppan
d753468617 Merge branch 'release/0.6.0' into develop 2021-02-17 06:49:55 +02:00
Luke Leppan
53a6452f2d Merge branch 'release/0.6.0' 2021-02-17 06:49:54 +02:00
Luke Leppan
ad9a1d0cda bump 2021-02-17 06:48:20 +02:00
Luke Leppan
a065905629 Update README 2021-02-17 06:44:12 +02:00
Luke Leppan
d329d26c4a Merge branch 'master' into develop 2021-02-17 06:29:45 +02:00
Luke Leppan
31c3de78c6 Fix Word Count for other lang 2021-02-17 06:29:24 +02:00
Luke Leppan
cb36debed4 Merge branch 'release/0.5.0' into develop 2021-01-19 22:53:24 +02:00
Luke Leppan
f466e27884 Add file count to non-markdown files #5 2021-01-20 00:41:11 +02:00
Luke Leppan
bc5b2f82e6 Merge branch 'hotfix/0.4.1' into develop 2021-01-19 23:21:00 +02:00
Luke Leppan
dc2cb36940 Merge branch 'release/0.4.0' into develop 2021-01-19 23:12:09 +02:00
Luke Leppan
b24b1e8154 Merge branch 'release/0.5.0' 2021-01-19 22:53:24 +02:00
Luke Leppan
02ef5383bf bump 2021-01-19 22:53:03 +02:00
27 changed files with 1849 additions and 283 deletions

View file

@ -70,3 +70,13 @@ jobs:
asset_path: ./manifest.json
asset_name: manifest.json
asset_content_type: application/json
- name: Upload CSS
id: upload-css
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./src/styles.css
asset_name: styles.css
asset_content_type: text/css

2
.gitignore vendored
View file

@ -11,3 +11,5 @@ main.js
*.js.map
test-vault/
*.zip
.DS_Store
dist/

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Luke Leppan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,9 +1,52 @@
## Better Word Count
# 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) ![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.
**Note:** This plugin has only been tested with English.
![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.
- Works with all languages.
- Can display a variety of different stats. Including:
- Words, Characters, Sentences, Footnotes, and Pandoc Citations in current file.
- Total Words, Characters, Sentences, Footnotes, Pandoc Citations, and Files in vault.
- Words, Characters, Sentences, Footnotes, and Pandoc Citations typed today.
- Highly Customizable status bar that can be adapted to your needs.
## Contributors
- @leoccyao
- Added all word, char, sentence count when not viewing a markdown file.
- @chrisgrieser
- Added Footnotes and Citation Counting.
- @bakuzan
- Added page counts.
- Fixed issue that caused errors at start up.
- @THeK3nger
- Fixed issue that occurred when renaming files.
- @lishid
- Helped solve the performance issue.
- @Noxellar
- Fixed alt bar spacing issue.
### Special Thanks
- @liamcane
- @eleanorkonik
- @AngelusDomini
- @archelpeg
- @aproximate
- @Quorafind
- @torantine
- @lammersma
- @aknighty74
- @dhruvik7
### Support Me
If you enjoyed the plugin you can support me by clicking the button below. I am currently a student so anything helps.
[<img src="https://cdn.buymeacoffee.com/buttons/v2/default-violet.png" alt="BuyMeACoffee" width="100">](https://www.buymeacoffee.com/lukeleppan)

View file

@ -1,7 +1,7 @@
{
"id": "better-word-count",
"name": "Better Word Count",
"version": "0.4.1",
"version": "0.9.6",
"description": "Counts the words of selected text in the editor.",
"author": "Luke Leppan",
"authorUrl": "https://lukeleppan.com",

View file

@ -1,12 +1,16 @@
{
"name": "better-word-count",
"version": "0.4.1",
"version": "0.9.6",
"description": "Counts the words of selected text in the editor.",
"main": "main.js",
"scripts": {
"lint": "svelte-check && eslint . --ext .ts",
"dev": "rollup --config rollup.config.js -w",
"build": "rollup --config rollup.config.js"
},
"repository": {
"url": "https://github.com/lukeleppan/better-word-count.git"
},
"keywords": [
"obsidian",
"obsidian-md",
@ -15,14 +19,29 @@
"author": "Luke Leppan",
"license": "MIT",
"devDependencies": {
"@codemirror/commands": "^6.1.2",
"@codemirror/language": "^6.3.0",
"@codemirror/search": "^6.2.2",
"@codemirror/state": "^6.1.2",
"@codemirror/text": "^0.19.6",
"@codemirror/view": "^6.4.0",
"@rollup/plugin-commonjs": "^15.1.0",
"@rollup/plugin-node-resolve": "^9.0.0",
"@rollup/plugin-typescript": "^6.0.0",
"@types/node": "^14.14.2",
"@tsconfig/svelte": "^1.0.13",
"@types/node": "^14.17.3",
"obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master",
"rollup": "^2.32.1",
"rollup-plugin-copy": "^3.3.0",
"rollup-plugin-svelte": "^7.1.0",
"svelte-check": "^2.2.0",
"svelte-preprocess": "^4.7.3",
"ts-node": "^9.1.1",
"tslib": "^2.0.3",
"typescript": "^4.0.3"
},
"dependencies": {
"svelte": "^3.38.3",
"svelte-icons": "^2.1.0"
}
}

5
presets/default.md Normal file
View file

@ -0,0 +1,5 @@
Query:
{word_count} words {character_count} characters
Alt Query:
{files} files {total_words} words {total_characters} characters

View file

@ -1,27 +1,63 @@
import typescript from '@rollup/plugin-typescript';
import {nodeResolve} from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import copy from 'rollup-plugin-copy';
const TEST_VAULT = 'test-vault/.obsidian/plugins/better-word-count';
import typescript from "@rollup/plugin-typescript";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import copy from "rollup-plugin-copy";
import svelte from "rollup-plugin-svelte";
import sveltePreprocess from "svelte-preprocess";
const TEST_VAULT = "test-vault/.obsidian/plugins/better-word-count";
export default {
input: 'src/main.ts',
input: "src/main.ts",
output: {
dir: 'dist/',
sourcemap: 'inline',
format: 'cjs',
exports: 'default'
dir: "dist/",
sourcemap: "inline",
format: "cjs",
exports: "default",
},
external: ['obsidian'],
external: [
"obsidian",
"electron",
"codemirror",
"@codemirror/autocomplete",
"@codemirror/closebrackets",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/comment",
"@codemirror/fold",
"@codemirror/gutter",
"@codemirror/highlight",
"@codemirror/history",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/matchbrackets",
"@codemirror/panel",
"@codemirror/rangeset",
"@codemirror/rectangular-selection",
"@codemirror/search",
"@codemirror/state",
"@codemirror/stream-parser",
"@codemirror/text",
"@codemirror/tooltip",
"@codemirror/view",
"@lezer/common",
"@lezer/lr",
],
plugins: [
typescript(),
nodeResolve({browser: true}),
nodeResolve({ browser: true }),
commonjs(),
svelte({
include: "src/**/*.svelte",
compilerOptions: { css: true },
preprocess: sveltePreprocess(),
}),
copy({
targets: [
{ src: 'dist/main.js', dest: TEST_VAULT },
{ src: ['manifest.json'], dest: TEST_VAULT }
], flatten: true
})
]
{ src: "src/styles.css", dest: TEST_VAULT },
{ src: "dist/main.js", dest: TEST_VAULT },
{ src: ["manifest.json"], dest: TEST_VAULT },
],
flatten: true,
}),
],
};

13
src/constants.ts Normal file
View file

@ -0,0 +1,13 @@
export const VIEW_TYPE_STATS = "vault-stats";
export const STATS_FILE = ".obsidian/vault-stats.json";
export const STATS_ICON = `<g transform="matrix(0.95,0,0,0.95,2.5,2.5)"><path fill="currentColor" stroke="currentColor" d="M3.77,100L22.421,100C24.503,100 26.19,98.013 26.19,95.561L26.19,34.813C26.19,32.361 24.503,30.374 22.421,30.374L3.77,30.374C1.688,30.374 -0,32.361 -0,34.813L-0,95.561C-0,98.013 1.688,100 3.77,100ZM40.675,100L59.325,100C61.408,100 63.095,98.013 63.095,95.561L63.095,4.439C63.095,1.987 61.408,-0 59.325,-0L40.675,-0C38.592,-0 36.905,1.987 36.905,4.439L36.905,95.561C36.905,98.013 38.592,100 40.675,100ZM77.579,100L96.23,100C98.312,100 100,98.013 100,95.561L100,46.495C100,44.043 98.312,42.056 96.23,42.056L77.579,42.056C75.497,42.056 73.81,44.043 73.81,46.495L73.81,95.561C73.81,98.013 75.497,100 77.579,100Z" style="fill:none;fill-rule:nonzero;stroke-width:8px;"/></g>`;
export const STATS_ICON_NAME = "stats-graph";
export const MATCH_HTML_COMMENT = new RegExp(
"<!--[\\s\\S]*?(?:-->)?" +
"<!---+>?" +
"|<!(?![dD][oO][cC][tT][yY][pP][eE]|\\[CDATA\\[)[^>]*>?" +
"|<[?][^>]*>?",
"g"
);
export const MATCH_COMMENT = new RegExp("%%[^%%]+%%", "g");
export const MATCH_PARAGRAPH = new RegExp("\n([^\n]+)\n", "g");

View file

@ -0,0 +1,76 @@
import { Transaction } from "@codemirror/state";
import {
ViewUpdate,
PluginValue,
EditorView,
ViewPlugin,
} from "@codemirror/view";
import type BetterWordCount from "src/main";
class EditorPlugin implements PluginValue {
hasPlugin: boolean;
view: EditorView;
private plugin: BetterWordCount;
constructor(view: EditorView) {
this.view = view;
this.hasPlugin = false;
}
update(update: ViewUpdate): void {
if (!this.hasPlugin) {
return;
}
const tr = update.transactions[0];
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") || userEventTypeUndefined) &&
tr.newSelection.ranges[0].from !== tr.newSelection.ranges[0].to
) {
let text = "";
const selection = tr.newSelection.main;
const textIter = tr.newDoc.iterRange(selection.from, selection.to);
while (!textIter.done) {
text = text + textIter.next().value;
}
this.plugin.statusBar.debounceStatusBarUpdate(text);
} else if (
tr.isUserEvent("input") ||
tr.isUserEvent("delete") ||
tr.isUserEvent("move") ||
tr.isUserEvent("undo") ||
tr.isUserEvent("redo") ||
tr.isUserEvent("select")
) {
const textIter = tr.newDoc.iter();
let text = "";
while (!textIter.done) {
text = text + textIter.next().value;
}
if (tr.docChanged && this.plugin.statsManager) {
this.plugin.statsManager.debounceChange(text);
}
this.plugin.statusBar.debounceStatusBarUpdate(text);
}
}
addPlugin(plugin: BetterWordCount) {
this.plugin = plugin;
this.hasPlugin = true;
}
destroy() {}
}
export const editorPlugin = ViewPlugin.fromClass(EditorPlugin);

View file

@ -1,136 +1,89 @@
import { MarkdownView, Plugin, TFile, WorkspaceSidedock } from "obsidian";
import { BetterWordCountSettingsTab } from "./settings/settings-tab";
import { BetterWordCountSettings } from "./settings/settings";
import { StatusBar } from "./status-bar";
import { Plugin, TFile, WorkspaceLeaf } from "obsidian";
import BetterWordCountSettingsTab from "./settings/SettingsTab";
import StatsManager from "./stats/StatsManager";
import StatusBar from "./status/StatusBar";
import type { EditorView } from "@codemirror/view";
import { editorPlugin } from "./editor/EditorPlugin";
import {
BetterWordCountSettings,
DEFAULT_SETTINGS,
} from "src/settings/Settings";
import { settingsStore } from "./utils/SvelteStores";
export default class BetterWordCount extends Plugin {
public recentlyTyped: boolean;
public statusBar: StatusBar;
public currentFile: TFile;
public settings: BetterWordCountSettings;
public statusBar: StatusBar;
public statsManager: StatsManager;
async onunload(): Promise<void> {
this.statsManager = null;
this.statusBar = null;
}
async onload() {
let statusBarEl = this.addStatusBarItem();
this.statusBar = new StatusBar(statusBarEl);
this.recentlyTyped = false;
this.settings = (await this.loadData()) || new BetterWordCountSettings();
// Settings Store
// 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));
this.registerEvent(
this.app.workspace.on("file-open", this.onFileOpen, this)
);
this.registerEvent(
this.app.workspace.on("quick-preview", this.onQuickPreview, this)
);
this.registerInterval(
window.setInterval(async () => {
let activeLeaf = this.app.workspace.activeLeaf;
if (!activeLeaf || !(activeLeaf.view instanceof MarkdownView)) {
return;
// Handle Statistics
if (this.settings.collectStats) {
this.statsManager = new StatsManager(this.app.vault, this.app.workspace, this);
}
let editor = activeLeaf.view.sourceMode.cmEditor;
if (editor.somethingSelected()) {
let content: string = editor.getSelection();
this.updateWordCount(content);
this.recentlyTyped = false;
} else if (
this.currentFile &&
this.currentFile.extension === "md" &&
!this.recentlyTyped
) {
const contents = await this.app.vault.cachedRead(this.currentFile);
this.updateWordCount(contents);
} else if (!this.recentlyTyped) {
this.updateWordCount("");
}
}, 500)
);
// Handle Status Bar
let statusBarEl = this.addStatusBarItem();
this.statusBar = new StatusBar(statusBarEl, this);
let activeLeaf = this.app.workspace.activeLeaf;
let files: TFile[] = this.app.vault.getMarkdownFiles();
// Handle the Editor Plugin
this.registerEditorExtension(editorPlugin);
files.forEach((file) => {
if (file.basename === activeLeaf.getDisplayText()) {
this.onFileOpen(file);
}
this.app.workspace.onLayoutReady(() => {
this.giveEditorPlugin(this.app.workspace.getMostRecentLeaf());
});
this.registerEvent(
this.app.workspace.on(
"active-leaf-change",
async (leaf: WorkspaceLeaf) => {
this.giveEditorPlugin(leaf);
if (leaf.view.getViewType() !== "markdown") {
this.statusBar.updateAltBar();
}
async onFileOpen(file: TFile) {
this.currentFile = file;
if (file && file.extension === "md") {
const contents = await this.app.vault.cachedRead(file);
this.recentlyTyped = true;
this.updateWordCount(contents);
} else {
this.updateWordCount("");
if (!this.settings.collectStats) return;
await this.statsManager.recalcTotals();
}
}
onQuickPreview(file: TFile, contents: string) {
this.currentFile = file;
const leaf = this.app.workspace.activeLeaf;
if (leaf && leaf.view.getViewType() === "markdown") {
this.recentlyTyped = true;
this.updateWordCount(contents);
}
}
updateWordCount(text: string) {
let words: number = 0;
const matches = text.match(
/[a-zA-Z0-9_\u0392-\u03c9\u00c0-\u00ff\u0600-\u06ff]+|[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af]+/gm
)
);
if (matches) {
for (let i = 0; i < matches.length; i++) {
if (matches[i].charCodeAt(0) > 19968) {
words += matches[i].length;
} else {
words += 1;
this.registerEvent(
this.app.vault.on("delete", async () => {
if (!this.settings.collectStats) return;
await this.statsManager.recalcTotals();
})
);
}
giveEditorPlugin(leaf: WorkspaceLeaf): void {
//@ts-expect-error, not typed
const editor = leaf?.view?.editor;
if (editor) {
const editorView = editor.cm as EditorView;
const editorPlug = editorView.plugin(editorPlugin);
editorPlug.addPlugin(this);
//@ts-expect-error, not typed
const data: string = leaf.view.data;
this.statusBar.updateStatusBar(data);
}
}
// Thanks to Extract Highlights plugin and AngelusDomini
// Also https://stackoverflow.com/questions/5553410
const sentences: number = (
(text || "").match(
/[^.!?\s][^.!?]*(?:[.!?](?!['"]?\s|$)[^.!?]*)*[.!?]?['"]?(?=\s|$)/gm
) || []
).length;
let displayText: string = "";
if (this.settings.showWords) {
displayText =
displayText +
this.settings.wordsPrefix +
words +
this.settings.wordsSuffix;
}
if (this.settings.showCharacters) {
displayText =
displayText +
this.settings.charactersPrefix +
text.length +
this.settings.charactersSuffix;
}
if (this.settings.showSentences) {
displayText =
displayText +
this.settings.sentencesPrefix +
sentences +
this.settings.sentencesSuffix;
}
this.statusBar.displayText(displayText);
async saveSettings(): Promise<void> {
await this.saveData(this.settings);
}
}

79
src/settings/Settings.ts Normal file
View file

@ -0,0 +1,79 @@
export enum MetricCounter {
words,
characters,
sentences,
footnotes,
citations,
pages,
files,
}
export enum MetricType {
file,
daily,
total,
folder,
}
export interface Metric {
type: MetricType;
counter: MetricCounter;
folder?: string;
}
export interface StatusBarItem {
prefix: string;
suffix: string;
metric: Metric;
}
export const BLANK_SB_ITEM: StatusBarItem = {
prefix: "",
suffix: "",
metric: {
type: null,
counter: null,
},
};
export interface BetterWordCountSettings {
statusBar: StatusBarItem[];
altBar: StatusBarItem[];
countComments: boolean;
collectStats: boolean;
pageWords: number;
}
export const DEFAULT_SETTINGS: BetterWordCountSettings = {
statusBar: [
{
prefix: "",
suffix: " words",
metric: {
type: MetricType.file,
counter: MetricCounter.words,
},
},
{
prefix: " ",
suffix: " characters",
metric: {
type: MetricType.file,
counter: MetricCounter.characters,
},
},
],
altBar: [
{
prefix: "",
suffix: " files",
metric: {
type: MetricType.total,
counter: MetricCounter.files,
},
},
],
countComments: false,
collectStats: false,
pageWords: 300,
};

View file

@ -0,0 +1,56 @@
import { App, PluginSettingTab, Setting, ToggleComponent, TextComponent } from "obsidian";
import type BetterWordCount from "src/main";
import { addStatusBarSettings } from "./StatusBarSettings";
export default class BetterWordCountSettingsTab extends PluginSettingTab {
constructor(app: App, private plugin: BetterWordCount) {
super(app, plugin);
}
display(): void {
let { containerEl } = this;
containerEl.empty();
containerEl.createEl("h3", { text: "Better Word Count Settings" });
// General Settings
containerEl.createEl("h4", { text: "General Settings" });
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.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);
cb.onChange(async (value: boolean) => {
this.plugin.settings.collectStats = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
.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) => {
this.plugin.settings.countComments = value;
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);
}
}

View file

@ -0,0 +1,448 @@
<script lang="ts">
import type { StatusBarItem } 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 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.footnotes:
return "Footnotes in Note"
case MetricCounter.citations:
return "Citations in Note"
case MetricCounter.pages:
return "Pages 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.footnotes:
return "Daily Footnotes"
case MetricCounter.citations:
return "Daily Citations"
case MetricCounter.pages:
return "Daily Pages"
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.footnotes:
return "Total Footnotes"
case MetricCounter.citations:
return "Total Citations"
case MetricCounter.pages:
return "Total Pages"
case MetricCounter.files:
return "Total Notes"
}
} else {
return "Select Options"
}
}
function swapStatusBarItems(i: number, j: number, arr: StatusBarItem[]) {
const max = arr.length - 1;
if (i < 0 || i > max || j < 0 || j > max) return arr;
const tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
return arr;
}
async function update(statusItems: StatusBarItem[]) {
plugin.settings.statusBar = statusItems.filter((item) => {
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, 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 () => {
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
</div>
</button>
</div>
{#each statusItems 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 () => {
statusItems = swapStatusBarItems(i, i-1, statusItems);
await update(statusItems);
}}
>
</button>
{/if}
{#if i !== statusItems.length - 1}
<button
aria-label="Move Status Bar Item Down"
on:click={async () => {
statusItems = swapStatusBarItems(i, i+1, statusItems);
await update(statusItems);
}}
>
</button>
{/if}
<button
aria-label="Remove Status Bar Item"
on:click={async () => {
statusItems = statusItems.filter((item, j) => i !== j);
await update(statusItems);
}}
>
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 update(statusItems);
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.footnotes}>Footnotes</option>
<option value={MetricCounter.citations}>Citations</option>
<option value={MetricCounter.pages}>Pages</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>
<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 update(statusItems);
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 update(statusItems);
await plugin.saveSettings();
}}
/>
</div>
</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.footnotes}>Footnotes</option>
<option value={MetricCounter.citations}>Citations</option>
<option value={MetricCounter.pages}>Pages</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

@ -0,0 +1,14 @@
import StatusBarSettings from "./StatusBarSettings.svelte";
import type BetterWordCount from "../main";
export function addStatusBarSettings(
plugin: BetterWordCount,
containerEl: HTMLElement
) {
const statusItemsEl = containerEl.createEl("div");
new StatusBarSettings({
target: statusItemsEl,
props: { plugin },
});
}

View file

@ -1,103 +0,0 @@
import { settings } from "cluster";
import { PluginSettingTab, Setting } from "obsidian";
import BetterWordCount from "../main";
export class BetterWordCountSettingsTab extends PluginSettingTab {
display(): void {
let { containerEl } = this;
const plugin: BetterWordCount = (this as any).plugin;
containerEl.empty();
containerEl.createEl("h2", { text: "Better Word Count Settings" });
// Word Count Settings
containerEl.createEl("h3", { text: "Word Count Settings" });
new Setting(containerEl)
.setName("Show Word Count")
.setDesc("Enable this to show the word count.")
.addToggle((boolean) =>
boolean.setValue(plugin.settings.showWords).onChange((value) => {
plugin.settings.showWords = value;
plugin.saveData(plugin.settings);
})
);
new Setting(containerEl)
.setName("Word Count Prefix")
.setDesc("This changes the text in front of the word count number.")
.addText((text) =>
text.setValue(plugin.settings.wordsPrefix).onChange((value) => {
plugin.settings.wordsPrefix = value;
plugin.saveData(plugin.settings);
})
);
new Setting(containerEl)
.setName("Word Count Suffix")
.setDesc("This changes the text after of the word count number.")
.addText((text) =>
text.setValue(plugin.settings.wordsSuffix).onChange((value) => {
plugin.settings.wordsSuffix = value;
plugin.saveData(plugin.settings);
})
);
// Character Count Settings
containerEl.createEl("h3", { text: "Character Count Settings" });
new Setting(containerEl)
.setName("Show Character Count")
.setDesc("Enable this to show the character count.")
.addToggle((boolean) =>
boolean.setValue(plugin.settings.showCharacters).onChange((value) => {
plugin.settings.showCharacters = value;
plugin.saveData(plugin.settings);
})
);
new Setting(containerEl)
.setName("Character Count Prefix")
.setDesc("This changes the text in front of the character count number.")
.addText((text) =>
text.setValue(plugin.settings.charactersPrefix).onChange((value) => {
plugin.settings.charactersPrefix = value;
plugin.saveData(plugin.settings);
})
);
new Setting(containerEl)
.setName("Character Count Suffix")
.setDesc("This changes the text after of the character count number.")
.addText((text) =>
text.setValue(plugin.settings.charactersSuffix).onChange((value) => {
plugin.settings.charactersSuffix = value;
plugin.saveData(plugin.settings);
})
);
// Sentence Count Settings
containerEl.createEl("h3", { text: "Sentence Count Settings" });
new Setting(containerEl)
.setName("Show Sentence Count")
.setDesc("Enable this to show the sentence count.")
.addToggle((boolean) =>
boolean.setValue(plugin.settings.showSentences).onChange((value) => {
plugin.settings.showSentences = value;
plugin.saveData(plugin.settings);
})
);
new Setting(containerEl)
.setName("Sentence Count Prefix")
.setDesc("This changes the text in front of the sentence count number.")
.addText((text) =>
text.setValue(plugin.settings.sentencesPrefix).onChange((value) => {
plugin.settings.sentencesPrefix = value;
plugin.saveData(plugin.settings);
})
);
new Setting(containerEl)
.setName("Sentence Count Suffix")
.setDesc("This changes the text after of the sentence count number.")
.addText((text) =>
text.setValue(plugin.settings.sentencesSuffix).onChange((value) => {
plugin.settings.sentencesSuffix = value;
plugin.saveData(plugin.settings);
})
);
}
}

View file

@ -1,11 +0,0 @@
export class BetterWordCountSettings {
showWords: boolean = true;
wordsPrefix: string = "";
wordsSuffix: string = " words ";
showCharacters: boolean = true;
charactersPrefix: string = "";
charactersSuffix: string = " characters ";
showSentences: boolean = false;
sentencesPrefix: string = "";
sentencesSuffix: string = " sentences";
}

38
src/stats/Stats.ts Normal file
View file

@ -0,0 +1,38 @@
export interface VaultStatistics {
history: History;
modifiedFiles: ModifiedFiles;
}
export type History = Record<string, Day>;
export interface Day {
words: number;
characters: number;
sentences: number;
pages: number;
files: number;
footnotes: number;
citations: number;
totalWords: number;
totalCharacters: number;
totalSentences: number;
totalFootnotes: number;
totalCitations: number;
totalPages: number;
}
export type ModifiedFiles = Record<string, FileStat>;
export interface FileStat {
footnotes: CountDiff;
citations: CountDiff;
words: CountDiff;
characters: CountDiff;
sentences: CountDiff;
pages: CountDiff;
}
export interface CountDiff {
initial: number;
current: number;
}

375
src/stats/StatsManager.ts Normal file
View file

@ -0,0 +1,375 @@
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,
cleanComments,
} from "../utils/StatUtils";
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, plugin: BetterWordCount) {
this.vault = vault;
this.workspace = workspace;
this.plugin = plugin;
this.debounceChange = debounce(
(text: string) => this.change(text),
50,
false
);
this.vault.on("rename", (new_name, old_path) => {
if (this.vaultStats.modifiedFiles.hasOwnProperty(old_path)) {
const content = this.vaultStats.modifiedFiles[old_path];
delete this.vaultStats.modifiedFiles[old_path];
this.vaultStats.modifiedFiles[new_name.path] = content;
}
});
this.vault.on("delete", (deleted_file) => {
if (this.vaultStats.modifiedFiles.hasOwnProperty(deleted_file.path)) {
delete this.vaultStats.modifiedFiles[deleted_file.path];
}
});
this.vault.adapter.exists(STATS_FILE).then(async (exists) => {
if (!exists) {
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));
} 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();
});
}
async update(): Promise<void> {
this.vault.adapter.write(STATS_FILE, JSON.stringify(this.vaultStats));
}
async updateToday(): Promise<void> {
if (this.vaultStats.history.hasOwnProperty(moment().format("YYYY-MM-DD"))) {
this.today = moment().format("YYYY-MM-DD");
return;
}
this.today = moment().format("YYYY-MM-DD");
const totalWords = await this.calcTotalWords();
const totalCharacters = await this.calcTotalCharacters();
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,
totalWords: totalWords,
totalCharacters: totalCharacters,
totalSentences: totalSentences,
totalFootnotes: totalFootnotes,
totalCitations: totalCitations,
totalPages: totalPages,
};
this.vaultStats.modifiedFiles = {};
this.vaultStats.history[this.today] = newDay;
await this.update();
}
public async change(text: string) {
if (this.plugin.settings.countComments) {
text = cleanComments(text);
}
const fileName = this.workspace.getActiveFile().path;
const currentWords = getWordCount(text);
const currentCharacters = getCharacterCount(text);
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")
) {
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].totalFootnotes +=
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 = currentFootnotes;
modFiles[fileName].citations.current = currentCitations;
modFiles[fileName].pages.current = currentPages;
} else {
modFiles[fileName] = {
words: {
initial: currentWords,
current: currentWords,
},
characters: {
initial: currentCharacters,
current: currentCharacters,
},
sentences: {
initial: currentSentences,
current: currentSentences,
},
footnotes: {
initial: currentFootnotes,
current: currentFootnotes,
},
citations: {
initial: currentCitations,
current: currentCitations,
},
pages: {
initial: currentPages,
current: currentPages,
},
};
}
const words = Object.values(modFiles)
.map((counts) =>
Math.max(0, counts.words.current - counts.words.initial)
)
.reduce((a, b) => a + b, 0);
const characters = Object.values(modFiles)
.map((counts) =>
Math.max(0, counts.characters.current - counts.characters.initial)
)
.reduce((a, b) => a + b, 0);
const sentences = Object.values(modFiles)
.map((counts) =>
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)
)
.reduce((a, b) => a + b, 0);
const citations = Object.values(modFiles)
.map((counts) =>
Math.max(0, counts.citations.current - counts.citations.initial)
).reduce((a, b) => a + b, 0);
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;
this.vaultStats.history[this.today].characters = characters;
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();
} else {
this.updateToday();
}
}
public async recalcTotals() {
if (!this.vaultStats) return;
if (
this.vaultStats.history.hasOwnProperty(this.today) &&
this.today === moment().format("YYYY-MM-DD")
) {
const todayHist: Day = this.vaultStats.history[this.today];
todayHist.totalWords = await this.calcTotalWords();
todayHist.totalCharacters = await this.calcTotalCharacters();
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();
}
}
private async calcTotalWords(): Promise<number> {
let words = 0;
const files = this.vault.getFiles();
for (const i in files) {
const file = files[i];
if (file.extension === "md") {
words += getWordCount(await this.vault.cachedRead(file));
}
}
return words;
}
private async calcTotalCharacters(): Promise<number> {
let characters = 0;
const files = this.vault.getFiles();
for (const i in files) {
const file = files[i];
if (file.extension === "md") {
characters += getCharacterCount(await this.vault.cachedRead(file));
}
}
return characters;
}
private async calcTotalSentences(): Promise<number> {
let sentence = 0;
const files = this.vault.getFiles();
for (const i in files) {
const file = files[i];
if (file.extension === "md") {
sentence += getSentenceCount(await this.vault.cachedRead(file));
}
}
return sentence;
}
private async calcTotalPages(): Promise<number> {
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<number> {
let footnotes = 0;
const files = this.vault.getFiles();
for (const i in files) {
const file = files[i];
if (file.extension === "md") {
footnotes += getFootnoteCount(await this.vault.cachedRead(file));
}
}
return footnotes;
}
private async calcTotalCitations(): Promise<number> {
let citations = 0;
const files = this.vault.getFiles();
for (const i in files) {
const file = files[i];
if (file.extension === "md") {
citations += getCitationCount(await this.vault.cachedRead(file));
}
}
return citations;
}
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 getDailyFootnotes(): number {
return this.vaultStats.history[this.today].footnotes;
}
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;
}
public async getTotalWords(): Promise<number> {
if (!this.vaultStats) return await this.calcTotalWords();
return this.vaultStats.history[this.today].totalWords;
}
public async getTotalCharacters(): Promise<number> {
if (!this.vaultStats) return await this.calcTotalCharacters();
return this.vaultStats.history[this.today].totalCharacters;
}
public async getTotalSentences(): Promise<number> {
if (!this.vaultStats) return await this.calcTotalSentences();
return this.vaultStats.history[this.today].totalSentences;
}
public async getTotalFootnotes(): Promise<number> {
if (!this.vaultStats) return await this.calcTotalFootnotes();
return this.vaultStats.history[this.today].totalFootnotes;
}
public async getTotalCitations(): Promise<number> {
if (!this.vaultStats) return await this.calcTotalCitations();
return this.vaultStats.history[this.today].totalCitations;
}
public async getTotalPages(): Promise<number> {
if (!this.vaultStats) return await this.calcTotalPages();
return this.vaultStats.history[this.today].totalPages;
}
}

View file

@ -1,11 +0,0 @@
export class StatusBar {
private statusBarEl: HTMLElement;
constructor(statusBarEl: HTMLElement) {
this.statusBarEl = statusBarEl;
}
displayText(text: string) {
this.statusBarEl.setText(text);
}
}

371
src/status/StatusBar.ts Normal file
View file

@ -0,0 +1,371 @@
import { MetricCounter, MetricType } from "src/settings/Settings";
import type BetterWordCount from "../main";
import {
getWordCount,
getCharacterCount,
getSentenceCount,
getCitationCount,
getFootnoteCount,
getPageCount,
cleanComments,
} from "src/utils/StatUtils";
import { debounce } from "obsidian";
export default class StatusBar {
private statusBarEl: HTMLElement;
private plugin: BetterWordCount;
public debounceStatusBarUpdate;
constructor(statusBarEl: HTMLElement, plugin: BetterWordCount) {
this.statusBarEl = statusBarEl;
this.plugin = plugin;
this.debounceStatusBarUpdate = debounce(
(text: string) => this.updateStatusBar(text),
20,
false
);
this.statusBarEl.classList.add("mod-clickable");
this.statusBarEl.setAttribute("aria-label", "!!!");
this.statusBarEl.setAttribute("aria-label-position", "top");
this.statusBarEl.addEventListener("click", (ev: MouseEvent) =>
this.onClick(ev)
);
}
onClick(ev: MouseEvent) {
ev;
}
displayText(text: string) {
this.statusBarEl.setText(text);
}
async updateStatusBar(text: string) {
const sb = this.plugin.settings.statusBar;
let display = "";
if (!this.plugin.settings.countComments) {
text = cleanComments(text);
}
for (let i = 0; i < sb.length; i++) {
const sbItem = sb[i];
display = display + sbItem.prefix;
const metric = sbItem.metric;
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.footnotes) {
switch (metric.type) {
case MetricType.file:
display = display + getFootnoteCount(text);
break;
case MetricType.daily:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getDailyFootnotes()
: 0);
break;
case MetricType.total:
display =
display +
(await (this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalFootnotes()
: 0));
break;
}
} else if (metric.counter === MetricCounter.citations) {
switch (metric.type) {
case MetricType.file:
display = display + getCitationCount(text);
break;
case MetricType.daily:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getDailyCitations()
: 0);
break;
case MetricType.total:
display =
display +
(await (this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalCitations()
: 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:
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.footnotes) {
switch (metric.type) {
case MetricType.file:
display = display + 0;
break;
case MetricType.daily:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getDailyFootnotes()
: 0);
break;
case MetricType.total:
display =
display +
(await (this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalFootnotes()
: 0));
break;
}
} else if (metric.counter === MetricCounter.citations) {
switch (metric.type) {
case MetricType.file:
display = display + 0;
break;
case MetricType.daily:
display =
display +
(this.plugin.settings.collectStats
? this.plugin.statsManager.getDailyCitations()
: 0);
break;
case MetricType.total:
display =
display +
(await (this.plugin.settings.collectStats
? this.plugin.statsManager.getTotalCitations()
: 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:
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);
}
}

21
src/styles.css Normal file
View file

@ -0,0 +1,21 @@
details.bwc-sb-item-setting {
border: 1px solid var(--background-modifier-border);
border-radius: 10px;
padding: 10px 5px 20px 10px;
margin-top: 5px;
margin-bottom: 10px;
}
.bwc-sb-item-setting summary::marker {
font-size: 10px;
}
/* .bwc-sb-item-setting summary { */
/* margin-bottom: 5px; */
/* } */
.bwc-sb-item-setting summary span.bwc-sb-buttons {
float: right;
}
.bwc-status-bar-settings-title {
margin-bottom: 0px;
}

67
src/utils/StatUtils.ts Normal file
View file

@ -0,0 +1,67 @@
import type { Vault } from "obsidian";
import { MATCH_HTML_COMMENT, MATCH_COMMENT } from "src/constants";
export function getWordCount(text: string): number {
const spaceDelimitedChars =
/'A-Za-z\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC/
.source;
const nonSpaceDelimitedWords =
/\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u4E00-\u9FD5/.source;
const nonSpaceDelimitedWordsOther =
/[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u4E00-\u9FD5]{1}/
.source;
const pattern = new RegExp(
[
`(?:[0-9]+(?:(?:,|\\.)[0-9]+)*|[\\-${spaceDelimitedChars}])+`,
nonSpaceDelimitedWords,
nonSpaceDelimitedWordsOther,
].join("|"),
"g"
);
return (text.match(pattern) || []).length;
}
export function getCharacterCount(text: string): number {
return text.length;
}
export function getFootnoteCount(text: string): number {
const regularFn = text.match(/\[\^\S+](?!:)/g);
const inlineFn = text.match(/\^\[[^^].+?]/g);
let overallFn = 0;
if (regularFn) overallFn += regularFn.length;
if (inlineFn) overallFn += inlineFn.length;
return overallFn;
}
export function getCitationCount(text: string): number {
const pandocCitations = text.match(/@[A-Za-z0-9-]+[,;\]](?!\()/gi);
if (!pandocCitations) return 0;
const uniqueCitations = [...new Set(pandocCitations)].length;
return uniqueCitations;
}
export function getSentenceCount(text: string): number {
const sentences: number = (
(text || "").match(
/[^.!?\s][^.!?]*(?:[.!?](?!['"]?\s|$)[^.!?]*)*[.!?]?['"]?(?=\s|$)/gm
) || []
).length;
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;
}
export function cleanComments(text: string): string {
return text.replace(MATCH_COMMENT, "").replace(MATCH_HTML_COMMENT, "");
}

View file

@ -0,0 +1,8 @@
import {
BetterWordCountSettings,
DEFAULT_SETTINGS,
} from "src/settings/Settings";
import { writable } from "svelte/store";
export const settingsStore =
writable<BetterWordCountSettings>(DEFAULT_SETTINGS);

View file

@ -0,0 +1,9 @@
<svelte:options immutable />
<script lang="ts">
</script>
<div id="statistics-container" class="container">
<h1>Coming Soon!</h1>
</div>

32
src/view/view.ts Normal file
View file

@ -0,0 +1,32 @@
import { ItemView, WorkspaceLeaf } from "obsidian";
import { STATS_ICON_NAME, VIEW_TYPE_STATS } from "src/constants";
import type BetterWordCount from "src/main";
//@ts-ignore
import Statistics from "./Statistics.svelte";
export default class StatsView extends ItemView {
private plugin: BetterWordCount;
private statistics: Statistics;
constructor(leaf: WorkspaceLeaf) {
super(leaf);
}
getViewType(): string {
return VIEW_TYPE_STATS;
}
getDisplayText(): string {
return "Statistics";
}
getIcon(): string {
return STATS_ICON_NAME;
}
async onOpen(): Promise<void> {
this.statistics = new Statistics({
target: (this as any).contentEl,
});
}
}

View file

@ -1,22 +1,17 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"types": ["node", "svelte"],
"inlineSources": true,
"module": "ESNext",
"target": "es5",
// "module": "ESNext",
// "target": "es5",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"lib": [
"dom",
"es5",
"scripthost",
"es2015"
]
"importHelpers": true
// "lib": ["dom", "es5", "scripthost", "es2015"]
},
"include": [
"**/*.ts"
]
"include": ["**/*.ts", "**/*.svelte"],
"exclude": ["node_modules/*"]
}