mirror of
https://github.com/Ellpeck/ObsidianSimpleTimeTracker.git
synced 2024-12-18 19:39:22 +01:00
parent
5b79425a50
commit
cbba377c60
7 changed files with 290 additions and 168 deletions
30
README.md
30
README.md
|
@ -12,6 +12,36 @@ Need help using the plugin? Feel free to join the Discord server!
|
|||
|
||||
[![Join the Discord server](https://ellpeck.de/res/discord-wide.png)](https://link.ellpeck.de/discordweb)
|
||||
|
||||
## 🔍 Tracker Data in Dataview
|
||||
Super Simple Time Tracker has a public API that can be used with [Dataview](https://blacksmithgu.github.io/obsidian-dataview/), specifically [DataviewJS](https://blacksmithgu.github.io/obsidian-dataview/api/intro/), which can be accessed using the following code:
|
||||
|
||||
```js
|
||||
dv.app.plugins.plugins["simple-time-tracker"].api;
|
||||
```
|
||||
|
||||
The following is a short example that uses DataviewJS to load all trackers in the vault and print the total duration of each tracker. You can also find this example in action [in the test vault]().
|
||||
|
||||
```js
|
||||
// get the time tracker plugin api instance
|
||||
let api = dv.app.plugins.plugins["simple-time-tracker"].api;
|
||||
|
||||
for (let page of dv.pages()) {
|
||||
// load trackers in the file with the given path
|
||||
let trackers = await api.loadAllTrackers(page.file.path);
|
||||
|
||||
if (trackers.length)
|
||||
dv.el("strong", "Trackers in " + page.file.name);
|
||||
|
||||
for (let { section, tracker } of trackers) {
|
||||
// print the total duration of the tracker
|
||||
let duration = api.getTotalDuration(tracker.entries);
|
||||
dv.el("p", api.formatDuration(duration));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A full list of the functions exposed through the API can be found [in the code]().
|
||||
|
||||
# 👀 What it does
|
||||
A time tracker is really just a special code block that stores information about the times you pressed the Start and End buttons on. Since time is tracked solely through timestamps, you can switch notes, close Obsidian or even shut down your device completely while the tracker is running! Once you come back, your time tracker will still be running.
|
||||
|
||||
|
|
22
src/main.ts
22
src/main.ts
|
@ -1,11 +1,21 @@
|
|||
import { MarkdownRenderChild, Plugin, TFile } from "obsidian";
|
||||
import { defaultSettings, SimpleTimeTrackerSettings } from "./settings";
|
||||
import { SimpleTimeTrackerSettingsTab } from "./settings-tab";
|
||||
import { displayTracker, loadTracker } from "./tracker";
|
||||
import { displayTracker, Entry, formatDuration, formatTimestamp, getDuration, getRunningEntry, getTotalDuration, isRunning, loadAllTrackers, loadTracker, orderedEntries } from "./tracker";
|
||||
|
||||
export default class SimpleTimeTrackerPlugin extends Plugin {
|
||||
|
||||
settings: SimpleTimeTrackerSettings;
|
||||
public api = {
|
||||
// verbatim versions of the functions found in tracker.ts with the same parameters
|
||||
loadTracker, loadAllTrackers, getDuration, getTotalDuration, getRunningEntry, isRunning,
|
||||
|
||||
// modified versions of the functions found in tracker.ts, with the number of required arguments reduced
|
||||
formatTimestamp: (timestamp: string) => formatTimestamp(timestamp, this.settings),
|
||||
formatDuration: (totalTime: number) => formatDuration(totalTime, this.settings),
|
||||
orderedEntries: (entries: Entry[]) => orderedEntries(entries, this.settings)
|
||||
};
|
||||
|
||||
private settings: SimpleTimeTrackerSettings;
|
||||
|
||||
async onload(): Promise<void> {
|
||||
await this.loadSettings();
|
||||
|
@ -14,7 +24,7 @@ export default class SimpleTimeTrackerPlugin extends Plugin {
|
|||
|
||||
this.registerMarkdownCodeBlockProcessor("simple-time-tracker", (s, e, i) => {
|
||||
e.empty();
|
||||
let component = new MarkdownRenderChild(e)
|
||||
let component = new MarkdownRenderChild(e);
|
||||
let tracker = loadTracker(s);
|
||||
|
||||
// Initial file name
|
||||
|
@ -28,13 +38,13 @@ export default class SimpleTimeTrackerPlugin extends Plugin {
|
|||
if (file instanceof TFile && oldPath === filePath) {
|
||||
filePath = file.path;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Register the event to remove on unload
|
||||
component.registerEvent(renameEventRef);
|
||||
|
||||
displayTracker(this.app, tracker, e, getFile, () => i.getSectionInfo(e), this.settings, component);
|
||||
i.addChild(component)
|
||||
displayTracker(tracker, e, getFile, () => i.getSectionInfo(e), this.settings, component);
|
||||
i.addChild(component);
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
|
|
260
src/tracker.ts
260
src/tracker.ts
|
@ -1,4 +1,4 @@
|
|||
import {moment, App, MarkdownSectionInformation, ButtonComponent, TextComponent, TFile, MarkdownRenderer, Component, MarkdownRenderChild} from "obsidian";
|
||||
import { moment, MarkdownSectionInformation, ButtonComponent, TextComponent, TFile, MarkdownRenderer, Component, MarkdownRenderChild } from "obsidian";
|
||||
import { SimpleTimeTrackerSettings } from "./settings";
|
||||
import { ConfirmModal } from "./confirm-modal";
|
||||
|
||||
|
@ -13,7 +13,7 @@ export interface Entry {
|
|||
subEntries: Entry[];
|
||||
}
|
||||
|
||||
export async function saveTracker(tracker: Tracker, app: App, fileName: string, section: MarkdownSectionInformation): Promise<void> {
|
||||
export async function saveTracker(tracker: Tracker, fileName: string, section: MarkdownSectionInformation): Promise<void> {
|
||||
let file = app.vault.getAbstractFileByPath(fileName) as TFile;
|
||||
if (!file)
|
||||
return;
|
||||
|
@ -42,9 +42,33 @@ export function loadTracker(json: string): Tracker {
|
|||
return { entries: [] };
|
||||
}
|
||||
|
||||
export async function loadAllTrackers(fileName: string): Promise<{ section: MarkdownSectionInformation, tracker: Tracker }[]> {
|
||||
let file = app.vault.getAbstractFileByPath(fileName);
|
||||
let content = (await app.vault.cachedRead(file as TFile)).split("\n");
|
||||
|
||||
let trackers: { section: MarkdownSectionInformation, tracker: Tracker }[] = [];
|
||||
let curr: Partial<MarkdownSectionInformation> | undefined;
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
let line = content[i];
|
||||
if (line.trimEnd() == "```simple-time-tracker") {
|
||||
curr = { lineStart: i + 1, text: "" };
|
||||
} else if (curr) {
|
||||
if (line.trimEnd() == "```") {
|
||||
curr.lineEnd = i - 1;
|
||||
let tracker = loadTracker(curr.text);
|
||||
trackers.push({ section: curr as MarkdownSectionInformation, tracker: tracker });
|
||||
curr = undefined;
|
||||
} else {
|
||||
curr.text += `${line}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return trackers;
|
||||
}
|
||||
|
||||
type GetFile = () => string;
|
||||
|
||||
export function displayTracker(app: App, tracker: Tracker, element: HTMLElement, getFile: GetFile, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings, component: MarkdownRenderChild): void {
|
||||
export function displayTracker(tracker: Tracker, element: HTMLElement, getFile: GetFile, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings, component: MarkdownRenderChild): void {
|
||||
|
||||
element.addClass("simple-time-tracker-container");
|
||||
// add start/stop controls
|
||||
|
@ -59,7 +83,7 @@ export function displayTracker(app: App, tracker: Tracker, element: HTMLElement,
|
|||
} else {
|
||||
startNewEntry(tracker, newSegmentNameBox.getValue());
|
||||
}
|
||||
await saveTracker(tracker, this.app, getFile(), getSectionInfo());
|
||||
await saveTracker(tracker, getFile(), getSectionInfo());
|
||||
});
|
||||
btn.buttonEl.addClass("simple-time-tracker-btn");
|
||||
let newSegmentNameBox = new TextComponent(element)
|
||||
|
@ -87,7 +111,7 @@ export function displayTracker(app: App, tracker: Tracker, element: HTMLElement,
|
|||
createEl("th"));
|
||||
|
||||
for (let entry of orderedEntries(tracker.entries, settings))
|
||||
addEditableTableRow(app, tracker, entry, table, newSegmentNameBox, running, getFile, getSectionInfo, settings, 0, component);
|
||||
addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, getFile, getSectionInfo, settings, 0, component);
|
||||
|
||||
// add copy buttons
|
||||
let buttons = element.createEl("div", { cls: "simple-time-tracker-bottom" });
|
||||
|
@ -111,6 +135,113 @@ export function displayTracker(app: App, tracker: Tracker, element: HTMLElement,
|
|||
}, 1000);
|
||||
}
|
||||
|
||||
export function getDuration(entry: Entry): number {
|
||||
if (entry.subEntries) {
|
||||
return getTotalDuration(entry.subEntries);
|
||||
} else {
|
||||
let endTime = entry.endTime ? moment(entry.endTime) : moment();
|
||||
return endTime.diff(moment(entry.startTime));
|
||||
}
|
||||
}
|
||||
|
||||
export function getTotalDuration(entries: Entry[]): number {
|
||||
let ret = 0;
|
||||
for (let entry of entries)
|
||||
ret += getDuration(entry);
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function isRunning(tracker: Tracker): boolean {
|
||||
return !!getRunningEntry(tracker.entries);
|
||||
}
|
||||
|
||||
export function getRunningEntry(entries: Entry[]): Entry {
|
||||
for (let entry of entries) {
|
||||
// if this entry has sub entries, check if one of them is running
|
||||
if (entry.subEntries) {
|
||||
let running = getRunningEntry(entry.subEntries);
|
||||
if (running)
|
||||
return running;
|
||||
} else {
|
||||
// if this entry has no sub entries and no end time, it's running
|
||||
if (!entry.endTime)
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function createMarkdownTable(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
|
||||
let table = [["Segment", "Start time", "End time", "Duration"]];
|
||||
for (let entry of orderedEntries(tracker.entries, settings))
|
||||
table.push(...createTableSection(entry, settings));
|
||||
table.push(["**Total**", "", "", `**${formatDuration(getTotalDuration(tracker.entries), settings)}**`]);
|
||||
|
||||
let ret = "";
|
||||
// calculate the width every column needs to look neat when monospaced
|
||||
let widths = Array.from(Array(4).keys()).map(i => Math.max(...table.map(a => a[i].length)));
|
||||
for (let r = 0; r < table.length; r++) {
|
||||
// add separators after first row
|
||||
if (r == 1)
|
||||
ret += "| " + Array.from(Array(4).keys()).map(i => "-".repeat(widths[i])).join(" | ") + " |\n";
|
||||
|
||||
let row: string[] = [];
|
||||
for (let i = 0; i < 4; i++)
|
||||
row.push(table[r][i].padEnd(widths[i], " "));
|
||||
ret += "| " + row.join(" | ") + " |\n";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function createCsv(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
|
||||
let ret = "";
|
||||
for (let entry of orderedEntries(tracker.entries, settings)) {
|
||||
for (let row of createTableSection(entry, settings))
|
||||
ret += row.join(settings.csvDelimiter) + "\n";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function orderedEntries(entries: Entry[], settings: SimpleTimeTrackerSettings): Entry[] {
|
||||
return settings.reverseSegmentOrder ? entries.slice().reverse() : entries;
|
||||
}
|
||||
|
||||
export function formatTimestamp(timestamp: string, settings: SimpleTimeTrackerSettings): string {
|
||||
return moment(timestamp).format(settings.timestampFormat);
|
||||
}
|
||||
|
||||
export function formatDuration(totalTime: number, settings: SimpleTimeTrackerSettings): string {
|
||||
let ret = "";
|
||||
let duration = moment.duration(totalTime);
|
||||
let hours = settings.fineGrainedDurations ? duration.hours() : Math.floor(duration.asHours());
|
||||
|
||||
if (settings.timestampDurations) {
|
||||
if (settings.fineGrainedDurations) {
|
||||
let days = Math.floor(duration.asDays());
|
||||
if (days > 0)
|
||||
ret += days + ".";
|
||||
}
|
||||
ret += `${hours.toString().padStart(2, "0")}:${duration.minutes().toString().padStart(2, "0")}:${duration.seconds().toString().padStart(2, "0")}`;
|
||||
} else {
|
||||
if (settings.fineGrainedDurations) {
|
||||
let years = Math.floor(duration.asYears());
|
||||
if (years > 0)
|
||||
ret += years + "y ";
|
||||
if (duration.months() > 0)
|
||||
ret += duration.months() + "M ";
|
||||
if (duration.days() > 0)
|
||||
ret += duration.days() + "d ";
|
||||
}
|
||||
if (hours > 0)
|
||||
ret += hours + "h ";
|
||||
if (duration.minutes() > 0)
|
||||
ret += duration.minutes() + "m ";
|
||||
ret += duration.seconds() + "s";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
function startSubEntry(entry: Entry, name: string): void {
|
||||
// if this entry is not split yet, we add its time as a sub-entry instead
|
||||
if (!entry.subEntries) {
|
||||
|
@ -157,42 +288,6 @@ function removeEntry(entries: Entry[], toRemove: Entry): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
function isRunning(tracker: Tracker): boolean {
|
||||
return !!getRunningEntry(tracker.entries);
|
||||
}
|
||||
|
||||
function getRunningEntry(entries: Entry[]): Entry {
|
||||
for (let entry of entries) {
|
||||
// if this entry has sub entries, check if one of them is running
|
||||
if (entry.subEntries) {
|
||||
let running = getRunningEntry(entry.subEntries);
|
||||
if (running)
|
||||
return running;
|
||||
} else {
|
||||
// if this entry has no sub entries and no end time, it's running
|
||||
if (!entry.endTime)
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getDuration(entry: Entry): number {
|
||||
if (entry.subEntries) {
|
||||
return getTotalDuration(entry.subEntries);
|
||||
} else {
|
||||
let endTime = entry.endTime ? moment(entry.endTime) : moment();
|
||||
return endTime.diff(moment(entry.startTime));
|
||||
}
|
||||
}
|
||||
|
||||
function getTotalDuration(entries: Entry[]): number {
|
||||
let ret = 0;
|
||||
for (let entry of entries)
|
||||
ret += getDuration(entry);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLElement, currentDiv: HTMLDivElement, settings: SimpleTimeTrackerSettings): void {
|
||||
let running = getRunningEntry(tracker.entries);
|
||||
if (running && !running.endTime) {
|
||||
|
@ -204,10 +299,6 @@ function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLE
|
|||
total.setText(formatDuration(getTotalDuration(tracker.entries), settings));
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp: string, settings: SimpleTimeTrackerSettings): string {
|
||||
return moment(timestamp).format(settings.timestampFormat);
|
||||
}
|
||||
|
||||
function formatEditableTimestamp(timestamp: string, settings: SimpleTimeTrackerSettings): string {
|
||||
return moment(timestamp).format(settings.editableTimestampFormat);
|
||||
}
|
||||
|
@ -216,37 +307,6 @@ function unformatEditableTimestamp(formatted: string, settings: SimpleTimeTracke
|
|||
return moment(formatted, settings.editableTimestampFormat).toISOString();
|
||||
}
|
||||
|
||||
function formatDuration(totalTime: number, settings: SimpleTimeTrackerSettings): string {
|
||||
let ret = "";
|
||||
let duration = moment.duration(totalTime);
|
||||
let hours = settings.fineGrainedDurations ? duration.hours() : Math.floor(duration.asHours());
|
||||
|
||||
if (settings.timestampDurations) {
|
||||
if (settings.fineGrainedDurations) {
|
||||
let days = Math.floor(duration.asDays());
|
||||
if (days > 0)
|
||||
ret += days + ".";
|
||||
}
|
||||
ret += `${hours.toString().padStart(2, "0")}:${duration.minutes().toString().padStart(2, "0")}:${duration.seconds().toString().padStart(2, "0")}`;
|
||||
} else {
|
||||
if (settings.fineGrainedDurations) {
|
||||
let years = Math.floor(duration.asYears());
|
||||
if (years > 0)
|
||||
ret += years + "y ";
|
||||
if (duration.months() > 0)
|
||||
ret += duration.months() + "M ";
|
||||
if (duration.days() > 0)
|
||||
ret += duration.days() + "d ";
|
||||
}
|
||||
if (hours > 0)
|
||||
ret += hours + "h ";
|
||||
if (duration.minutes() > 0)
|
||||
ret += duration.minutes() + "m ";
|
||||
ret += duration.seconds() + "s";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function fixLegacyTimestamps(entries: Entry[]): void {
|
||||
for (let entry of entries) {
|
||||
if (entry.startTime && !isNaN(+entry.startTime))
|
||||
|
@ -259,36 +319,6 @@ function fixLegacyTimestamps(entries: Entry[]): void {
|
|||
}
|
||||
}
|
||||
|
||||
function createMarkdownTable(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
|
||||
let table = [["Segment", "Start time", "End time", "Duration"]];
|
||||
for (let entry of orderedEntries(tracker.entries, settings))
|
||||
table.push(...createTableSection(entry, settings));
|
||||
table.push(["**Total**", "", "", `**${formatDuration(getTotalDuration(tracker.entries), settings)}**`]);
|
||||
|
||||
let ret = "";
|
||||
// calculate the width every column needs to look neat when monospaced
|
||||
let widths = Array.from(Array(4).keys()).map(i => Math.max(...table.map(a => a[i].length)));
|
||||
for (let r = 0; r < table.length; r++) {
|
||||
// add separators after first row
|
||||
if (r == 1)
|
||||
ret += "| " + Array.from(Array(4).keys()).map(i => "-".repeat(widths[i])).join(" | ") + " |\n";
|
||||
|
||||
let row: string[] = [];
|
||||
for (let i = 0; i < 4; i++)
|
||||
row.push(table[r][i].padEnd(widths[i], " "));
|
||||
ret += "| " + row.join(" | ") + " |\n";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function createCsv(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
|
||||
let ret = "";
|
||||
for (let entry of orderedEntries(tracker.entries, settings)) {
|
||||
for (let row of createTableSection(entry, settings))
|
||||
ret += row.join(settings.csvDelimiter) + "\n";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function createTableSection(entry: Entry, settings: SimpleTimeTrackerSettings): string[][] {
|
||||
let ret = [[
|
||||
|
@ -303,11 +333,7 @@ function createTableSection(entry: Entry, settings: SimpleTimeTrackerSettings):
|
|||
return ret;
|
||||
}
|
||||
|
||||
function orderedEntries(entries: Entry[], settings: SimpleTimeTrackerSettings): Entry[] {
|
||||
return settings.reverseSegmentOrder ? entries.slice().reverse() : entries;
|
||||
}
|
||||
|
||||
function addEditableTableRow(app: App, tracker: Tracker, entry: Entry, table: HTMLTableElement, newSegmentNameBox: TextComponent, trackerRunning: boolean, getFile: GetFile, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings, indent: number, component: MarkdownRenderChild): void {
|
||||
function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableElement, newSegmentNameBox: TextComponent, trackerRunning: boolean, getFile: GetFile, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings, indent: number, component: MarkdownRenderChild): void {
|
||||
let entryRunning = getRunningEntry(tracker.entries) == entry;
|
||||
let row = table.createEl("tr");
|
||||
|
||||
|
@ -328,7 +354,7 @@ function addEditableTableRow(app: App, tracker: Tracker, entry: Entry, table: HT
|
|||
.setDisabled(trackerRunning)
|
||||
.onClick(async () => {
|
||||
startSubEntry(entry, newSegmentNameBox.getValue());
|
||||
await saveTracker(tracker, this.app, getFile(), getSectionInfo());
|
||||
await saveTracker(tracker, getFile(), getSectionInfo());
|
||||
});
|
||||
let editButton = new ButtonComponent(entryButtons)
|
||||
.setClass("clickable-icon")
|
||||
|
@ -343,7 +369,7 @@ function addEditableTableRow(app: App, tracker: Tracker, entry: Entry, table: HT
|
|||
endField.endEdit();
|
||||
entry.endTime = endField.getTimestamp();
|
||||
}
|
||||
await saveTracker(tracker, this.app, getFile(), getSectionInfo());
|
||||
await saveTracker(tracker, getFile(), getSectionInfo());
|
||||
editButton.setIcon("lucide-pencil");
|
||||
|
||||
renderNameAsMarkdown(nameField.label, getFile, component);
|
||||
|
@ -365,23 +391,23 @@ function addEditableTableRow(app: App, tracker: Tracker, entry: Entry, table: HT
|
|||
.setDisabled(entryRunning)
|
||||
.onClick(async () => {
|
||||
|
||||
const confirmed = await showConfirm(app, "Are you sure you want to delete this entry?")
|
||||
const confirmed = await showConfirm("Are you sure you want to delete this entry?");
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeEntry(tracker.entries, entry);
|
||||
await saveTracker(tracker, this.app, getFile(), getSectionInfo());
|
||||
await saveTracker(tracker, getFile(), getSectionInfo());
|
||||
});
|
||||
|
||||
if (entry.subEntries) {
|
||||
for (let sub of orderedEntries(entry.subEntries, settings))
|
||||
addEditableTableRow(app, tracker, sub, table, newSegmentNameBox, trackerRunning, getFile, getSectionInfo, settings, indent + 1, component);
|
||||
addEditableTableRow(tracker, sub, table, newSegmentNameBox, trackerRunning, getFile, getSectionInfo, settings, indent + 1, component);
|
||||
}
|
||||
}
|
||||
|
||||
function showConfirm(app: App, message: string): Promise<boolean> {
|
||||
function showConfirm(message: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const modal = new ConfirmModal(app, message, resolve);
|
||||
modal.open();
|
||||
|
|
3
test-vault/.obsidian/community-plugins.json
vendored
3
test-vault/.obsidian/community-plugins.json
vendored
|
@ -1,3 +1,4 @@
|
|||
[
|
||||
"simple-time-tracker"
|
||||
"simple-time-tracker",
|
||||
"dataview"
|
||||
]
|
50
test-vault/.obsidian/core-plugins.json
vendored
50
test-vault/.obsidian/core-plugins.json
vendored
|
@ -1,20 +1,30 @@
|
|||
[
|
||||
"file-explorer",
|
||||
"global-search",
|
||||
"switcher",
|
||||
"graph",
|
||||
"backlink",
|
||||
"canvas",
|
||||
"outgoing-link",
|
||||
"tag-pane",
|
||||
"page-preview",
|
||||
"daily-notes",
|
||||
"templates",
|
||||
"note-composer",
|
||||
"command-palette",
|
||||
"editor-status",
|
||||
"bookmarks",
|
||||
"outline",
|
||||
"word-count",
|
||||
"file-recovery"
|
||||
]
|
||||
{
|
||||
"file-explorer": true,
|
||||
"global-search": true,
|
||||
"switcher": true,
|
||||
"graph": true,
|
||||
"backlink": true,
|
||||
"outgoing-link": true,
|
||||
"tag-pane": true,
|
||||
"page-preview": true,
|
||||
"daily-notes": true,
|
||||
"templates": true,
|
||||
"note-composer": true,
|
||||
"command-palette": true,
|
||||
"slash-command": false,
|
||||
"editor-status": true,
|
||||
"markdown-importer": false,
|
||||
"zk-prefixer": false,
|
||||
"random-note": false,
|
||||
"outline": true,
|
||||
"word-count": true,
|
||||
"slides": false,
|
||||
"audio-recorder": false,
|
||||
"workspaces": false,
|
||||
"file-recovery": true,
|
||||
"publish": false,
|
||||
"sync": false,
|
||||
"canvas": true,
|
||||
"bookmarks": true,
|
||||
"properties": false
|
||||
}
|
27
test-vault/.obsidian/plugins/dataview/data.json
vendored
Normal file
27
test-vault/.obsidian/plugins/dataview/data.json
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"renderNullAs": "\\-",
|
||||
"taskCompletionTracking": false,
|
||||
"taskCompletionUseEmojiShorthand": false,
|
||||
"taskCompletionText": "completion",
|
||||
"taskCompletionDateFormat": "yyyy-MM-dd",
|
||||
"recursiveSubTaskCompletion": false,
|
||||
"warnOnEmptyResult": true,
|
||||
"refreshEnabled": true,
|
||||
"refreshInterval": 2500,
|
||||
"defaultDateFormat": "MMMM dd, yyyy",
|
||||
"defaultDateTimeFormat": "h:mm a - MMMM dd, yyyy",
|
||||
"maxRecursiveRenderDepth": 4,
|
||||
"tableIdColumnName": "File",
|
||||
"tableGroupColumnName": "Group",
|
||||
"showResultCount": true,
|
||||
"allowHtml": true,
|
||||
"inlineQueryPrefix": "=",
|
||||
"inlineJsQueryPrefix": "$=",
|
||||
"inlineQueriesInCodeblocks": true,
|
||||
"enableInlineDataview": true,
|
||||
"enableDataviewJs": true,
|
||||
"enableInlineDataviewJs": true,
|
||||
"prettyRenderInlineFields": true,
|
||||
"prettyRenderInlineFieldsInLivePreview": true,
|
||||
"dataviewJsKeyword": "dataviewjs"
|
||||
}
|
18
test-vault/dataview-test.md
Normal file
18
test-vault/dataview-test.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
```dataviewjs
|
||||
// get the time tracker plugin api instance
|
||||
let api = dv.app.plugins.plugins["simple-time-tracker"].api;
|
||||
|
||||
for(let page of dv.pages()) {
|
||||
// load trackers in the file with the given path
|
||||
let trackers = await api.loadAllTrackers(page.file.path);
|
||||
|
||||
if (trackers.length)
|
||||
dv.el("strong", "Trackers in " + page.file.name);
|
||||
|
||||
for (let {section, tracker} of trackers) {
|
||||
// print the total duration of the tracker
|
||||
let duration = api.getTotalDuration(tracker.entries);
|
||||
dv.el("p", api.formatDuration(duration))
|
||||
}
|
||||
}
|
||||
```
|
Loading…
Reference in a new issue