mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
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. Currently the historical logs are available in the data.json, they will be integrated visually into the application in the next update.
|
||||
|
||||
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 |
12
package.json
12
package.json
@@ -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, debounce, Debouncer } 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;
|
||||
@@ -22,6 +24,8 @@ export default class DailyStats extends Plugin {
|
||||
today: string;
|
||||
debouncedUpdate: Debouncer<[contents: string, filepath: string]>;
|
||||
|
||||
private view: StatsTrackerView;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
@@ -37,6 +41,24 @@ export default class DailyStats extends Plugin {
|
||||
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))
|
||||
);
|
||||
@@ -51,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() {
|
||||
@@ -118,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