mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4f747d65c | ||
|
|
1cd5557787 | ||
|
|
7cd552848f | ||
|
|
3452c7b3a2 | ||
|
|
d8eee206c7 | ||
|
|
2587ff5820 | ||
|
|
4874cf4d4f | ||
|
|
09305323cb | ||
|
|
caa732e423 | ||
|
|
c3543942a3 | ||
|
|
7d1dc84610 | ||
|
|
f94b207a77 |
15
README.md
15
README.md
@@ -1,7 +1,8 @@
|
||||
## Obsidian Daily Stats
|
||||
|
||||
This is a daily word count plugin for Obsidian (https://obsidian.md). You can see today's word count in the bottom right corner of your screen, and also see the historical logs.
|
||||
|
||||
This plugin was inspired by liamcain's [Calender](https://github.com/liamcain/obsidian-calendar-plugin) and lukeleppan's [Better Word Count](https://github.com/lukeleppan/better-word-count).
|
||||
|
||||

|
||||
## Obsidian Daily Stats
|
||||
|
||||
This is a daily word count plugin for Obsidian (https://obsidian.md). You can see today's word count in the bottom right corner of your screen, and also see the historical logs in a right panel view akin to Github's contribution graph.
|
||||
|
||||
This plugin was inspired by liamcain's [Calender](https://github.com/liamcain/obsidian-calendar-plugin) and lukeleppan's [Better Word Count](https://github.com/lukeleppan/better-word-count).
|
||||
|
||||

|
||||
|
||||
|
||||
BIN
images/example-graph.png
Normal file
BIN
images/example-graph.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 227 KiB |
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"id": "obsidian-daily-stats",
|
||||
"name": "Daily Stats",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.9.10",
|
||||
"version": "1.0.1",
|
||||
"minAppVersion": "0.10.12",
|
||||
"description": "Track your daily word count across all notes in your vault.",
|
||||
"author": "Dhruvik Parikh",
|
||||
"authorUrl": "https://github.com/dhruvik7",
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-daily-stats",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "This is an Obsidian.md plugin that lets you view your daily word count.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
@@ -15,9 +15,17 @@
|
||||
"@rollup/plugin-node-resolve": "^9.0.0",
|
||||
"@rollup/plugin-typescript": "^6.0.0",
|
||||
"@types/node": "^14.14.2",
|
||||
"@types/react-calendar-heatmap": "^1.6.2",
|
||||
"@types/react-dom": "^17.0.1",
|
||||
"obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master",
|
||||
"postcss": "^8.2.6",
|
||||
"react": "^17.0.1",
|
||||
"react-calendar-heatmap": "^1.8.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"rollup": "^2.32.1",
|
||||
"rollup-plugin-postcss": "^4.0.0",
|
||||
"tslib": "^2.0.3",
|
||||
"typescript": "^4.0.3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import {nodeResolve} from '@rollup/plugin-node-resolve';
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import postcss from 'rollup-plugin-postcss'
|
||||
|
||||
export default {
|
||||
input: 'main.ts',
|
||||
input: 'src/main.ts',
|
||||
output: {
|
||||
dir: '.',
|
||||
sourcemap: 'inline',
|
||||
format: 'cjs',
|
||||
exports: 'default'
|
||||
},
|
||||
external: ['obsidian'],
|
||||
external: ['obsidian', 'crypto'],
|
||||
plugins: [
|
||||
typescript(),
|
||||
nodeResolve({browser: true}),
|
||||
nodeResolve({ browser: true, preferBuiltins: true }),
|
||||
commonjs(),
|
||||
postcss({
|
||||
plugins: []
|
||||
})
|
||||
]
|
||||
};
|
||||
54
src/calendar.tsx
Normal file
54
src/calendar.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as React from "react";
|
||||
import ReactCalendarHeatmap from "react-calendar-heatmap";
|
||||
import { MAX_COLORS, COLOR_FREQ } from "./constants";
|
||||
|
||||
interface HeatmapProps {
|
||||
data: any[];
|
||||
}
|
||||
|
||||
class Heatmap extends React.Component<HeatmapProps> {
|
||||
render() {
|
||||
const element = document.getElementById("color-elem");
|
||||
if (element) {
|
||||
const base = getComputedStyle(element).getPropertyValue("color");
|
||||
for (let elem of Array.from(document.getElementsByClassName("color1") as HTMLCollectionOf<HTMLElement>)) {
|
||||
elem.style.fill = base;
|
||||
elem.style.opacity = "0.44";
|
||||
}
|
||||
for (let elem of Array.from(document.getElementsByClassName("color2") as HTMLCollectionOf<HTMLElement>)) {
|
||||
elem.style.fill = base;
|
||||
elem.style.opacity = "0.6";
|
||||
}
|
||||
for (let elem of Array.from(document.getElementsByClassName("color3") as HTMLCollectionOf<HTMLElement>)) {
|
||||
elem.style.fill = base;
|
||||
elem.style.opacity = "0.76";
|
||||
}
|
||||
for (let elem of Array.from(document.getElementsByClassName("color4") as HTMLCollectionOf<HTMLElement>)) {
|
||||
elem.style.fill = base;
|
||||
elem.style.opacity = "0.92";
|
||||
}
|
||||
}
|
||||
return <div style={{ padding: "10px 0px 0px 10px", maxWidth: "300px", marginLeft: "auto", marginRight: "auto", fontSize: "4px !important" }} id="calendar-container">
|
||||
<ReactCalendarHeatmap
|
||||
startDate={new Date(new Date().setFullYear(new Date().getFullYear() - 1))}
|
||||
endDate={new Date()}
|
||||
values={this.props.data}
|
||||
horizontal={false}
|
||||
showMonthLabels={true}
|
||||
showWeekdayLabels={true}
|
||||
weekdayLabels={["S", "M", "T", "W", "T", "F", "S"]}
|
||||
classForValue={(value) => {
|
||||
if (!value || value.count == 0) {
|
||||
return 'color-empty';
|
||||
}
|
||||
return `color${Math.min(MAX_COLORS, Math.floor(Math.log(value.count) / Math.log(COLOR_FREQ)))}`;
|
||||
}}
|
||||
titleForValue={(value) => !value || value.date === null ? '' : value.count + ' words on ' + new Date(value.date).toLocaleDateString()}
|
||||
/>
|
||||
<div id="color-elem" />
|
||||
</div>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Heatmap;
|
||||
3
src/constants.ts
Normal file
3
src/constants.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const VIEW_TYPE_STATS_TRACKER = "stats-tracker";
|
||||
export const MAX_COLORS = 5;
|
||||
export const COLOR_FREQ = 6;
|
||||
@@ -1,4 +1,6 @@
|
||||
import { TFile, Plugin, MarkdownView } from 'obsidian';
|
||||
import { TFile, Plugin, MarkdownView, debounce, Debouncer, WorkspaceLeaf, addIcon } from 'obsidian';
|
||||
import { VIEW_TYPE_STATS_TRACKER } from './constants';
|
||||
import StatsTrackerView from './view';
|
||||
|
||||
interface WordCount {
|
||||
initial: number;
|
||||
@@ -20,6 +22,9 @@ export default class DailyStats extends Plugin {
|
||||
statusBarEl: HTMLElement;
|
||||
currentWordCount: number;
|
||||
today: string;
|
||||
debouncedUpdate: Debouncer<[contents: string, filepath: string]>;
|
||||
|
||||
private view: StatsTrackerView;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
@@ -32,6 +37,28 @@ export default class DailyStats extends Plugin {
|
||||
this.currentWordCount = 0;
|
||||
}
|
||||
|
||||
this.debouncedUpdate = debounce((contents: string, filepath: string) => {
|
||||
this.updateWordCount(contents, filepath);
|
||||
}, 400, false);
|
||||
|
||||
this.registerView(
|
||||
VIEW_TYPE_STATS_TRACKER,
|
||||
(leaf: WorkspaceLeaf) => (this.view = new StatsTrackerView(leaf, this.settings.dayCounts))
|
||||
);
|
||||
|
||||
this.addCommand({
|
||||
id: "show-daily-stats-tracker-view",
|
||||
name: "Open tracker view",
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return (
|
||||
this.app.workspace.getLeavesOfType(VIEW_TYPE_STATS_TRACKER).length === 0
|
||||
);
|
||||
}
|
||||
this.initLeaf();
|
||||
},
|
||||
});
|
||||
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("quit", this.onunload.bind(this))
|
||||
);
|
||||
@@ -46,10 +73,28 @@ export default class DailyStats extends Plugin {
|
||||
}, 200)
|
||||
);
|
||||
|
||||
addIcon("bar-graph", `<path fill="currentColor" stroke="currentColor" d="M122.88,105.98H9.59v-0.02c-2.65,0-5.05-1.08-6.78-2.81c-1.72-1.72-2.79-4.11-2.79-6.75H0V0h12.26v93.73h110.62V105.98 L122.88,105.98z M83.37,45.6h19.55c1.04,0,1.89,0.85,1.89,1.89v38.46c0,1.04-0.85,1.89-1.89,1.89H83.37 c-1.04,0-1.89-0.85-1.89-1.89V47.5C81.48,46.46,82.33,45.6,83.37,45.6L83.37,45.6z M25.36,22.07h19.55c1.04,0,1.89,0.85,1.89,1.89 v62c0,1.04-0.85,1.89-1.89,1.89H25.36c-1.04,0-1.89-0.85-1.89-1.89v-62C23.47,22.92,24.32,22.07,25.36,22.07L25.36,22.07 L25.36,22.07z M54.37,8.83h19.54c1.04,0,1.89,0.85,1.89,1.89v75.24c0,1.04-0.85,1.89-1.89,1.89H54.37c-1.04,0-1.89-0.85-1.89-1.89 V10.72C52.48,9.68,53.33,8.83,54.37,8.83L54.37,8.83z"/>`);
|
||||
this.registerInterval(window.setInterval(() => {
|
||||
this.updateDate();
|
||||
this.saveSettings();
|
||||
}, 1000));
|
||||
|
||||
if (this.app.workspace.layoutReady) {
|
||||
this.initLeaf();
|
||||
} else {
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("layout-ready", this.initLeaf.bind(this))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
initLeaf(): void {
|
||||
if (this.app.workspace.getLeavesOfType(VIEW_TYPE_STATS_TRACKER).length) {
|
||||
return;
|
||||
}
|
||||
this.app.workspace.getRightLeaf(false).setViewState({
|
||||
type: VIEW_TYPE_STATS_TRACKER,
|
||||
});
|
||||
}
|
||||
|
||||
async onunload() {
|
||||
@@ -58,7 +103,7 @@ export default class DailyStats extends Plugin {
|
||||
|
||||
onQuickPreview(file: TFile, contents: string) {
|
||||
if (this.app.workspace.getActiveViewOfType(MarkdownView)) {
|
||||
this.updateWordCount(contents, file.path);
|
||||
this.debouncedUpdate(contents, file.path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +158,8 @@ export default class DailyStats extends Plugin {
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
if (Object.keys(this.settings.dayCounts).length > 0) { //ensuring we never reset the data by accident
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/view.ts
Normal file
45
src/view.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { ItemView, WorkspaceLeaf } from "obsidian";
|
||||
import { VIEW_TYPE_STATS_TRACKER } from "./constants";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import * as React from "react";
|
||||
import Calendar from "./calendar";
|
||||
import '../styles.css';
|
||||
|
||||
export default class StatsTrackerView extends ItemView {
|
||||
private dayCounts: Record<string, number>;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, dayCounts: Record<string, number>) {
|
||||
super(leaf);
|
||||
this.dayCounts = dayCounts;
|
||||
|
||||
this.registerInterval(
|
||||
window.setInterval(() => {
|
||||
ReactDOM.render(React.createElement(Calendar, {
|
||||
data: Object.keys(this.dayCounts).map(day => {
|
||||
return { "date": new Date(new Date(day).setMonth(new Date(day).getMonth() + 1)), "count": this.dayCounts[day] }
|
||||
}),
|
||||
}), (this as any).contentEl);
|
||||
}, 1000)
|
||||
);
|
||||
}
|
||||
|
||||
getDisplayText() {
|
||||
return "Daily Stats";
|
||||
}
|
||||
|
||||
getIcon() {
|
||||
return "bar-graph";
|
||||
}
|
||||
|
||||
getViewType() {
|
||||
return VIEW_TYPE_STATS_TRACKER;
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
ReactDOM.render(React.createElement(Calendar, {
|
||||
data: Object.keys(this.dayCounts).map(day => {
|
||||
return { "date": new Date(new Date(day).setMonth(new Date(day).getMonth() + 1)), "count": this.dayCounts[day] }
|
||||
}),
|
||||
}), (this as any).contentEl);
|
||||
}
|
||||
}
|
||||
24
styles.css
24
styles.css
@@ -0,0 +1,24 @@
|
||||
text {
|
||||
fill: #aaa;
|
||||
}
|
||||
|
||||
rect:hover {
|
||||
stroke: #555;
|
||||
stroke-width: 0.5px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Default color scale
|
||||
*/
|
||||
|
||||
.color-empty {
|
||||
fill: var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.color-filled {
|
||||
fill: var(--text-accent);
|
||||
}
|
||||
|
||||
#color-elem {
|
||||
color: var(--text-accent);
|
||||
}
|
||||
@@ -13,10 +13,13 @@
|
||||
"dom",
|
||||
"es5",
|
||||
"scripthost",
|
||||
"es2015"
|
||||
]
|
||||
"es2015",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"jsx": "react",
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user