Compare commits

..

4 commits

20 changed files with 2418 additions and 3093 deletions

View file

@ -1,8 +1,8 @@
{ {
"id": "simple-time-tracker", "id": "simple-time-tracker",
"name": "Super Simple Time Tracker", "name": "Super Simple Time Tracker",
"version": "0.1.6", "version": "0.1.7",
"minAppVersion": "0.15.0", "minAppVersion": "1.2.8",
"description": "Multi-purpose time trackers for your notes!", "description": "Multi-purpose time trackers for your notes!",
"author": "Ellpeck", "author": "Ellpeck",
"authorUrl": "https://ellpeck.de", "authorUrl": "https://ellpeck.de",

1403
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "simple-time-tracker", "name": "simple-time-tracker",
"version": "0.1.6", "version": "0.1.7",
"description": "Multi-purpose time trackers for your notes!", "description": "Multi-purpose time trackers for your notes!",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

View file

@ -15,7 +15,7 @@ export default class SimpleTimeTrackerPlugin extends Plugin {
this.registerMarkdownCodeBlockProcessor("simple-time-tracker", (s, e, i) => { this.registerMarkdownCodeBlockProcessor("simple-time-tracker", (s, e, i) => {
let tracker: Tracker = loadTracker(s); let tracker: Tracker = loadTracker(s);
e.empty(); e.empty();
displayTracker(tracker, e, () => i.getSectionInfo(e), this.settings); displayTracker(tracker, e, i.sourcePath, () => i.getSectionInfo(e), this.settings);
}); });
this.addCommand({ this.addCommand({

View file

@ -41,6 +41,17 @@ export class SimpleTimeTrackerSettingsTab extends PluginSettingTab {
}); });
}); });
new Setting(this.containerEl)
.setName("Fine-Grained Durations")
.setDesc("Whether durations should include days, months and years. If this is disabled, additional time units will be displayed as part of the hours.")
.addToggle(t => {
t.setValue(this.plugin.settings.fineGrainedDurations);
t.onChange(async v => {
this.plugin.settings.fineGrainedDurations = v;
await this.plugin.saveSettings();
});
});
this.containerEl.createEl("hr"); this.containerEl.createEl("hr");
this.containerEl.createEl("p", {text: "If you like this plugin and want to support its development, you can do so through my website by clicking this fancy image!"}); this.containerEl.createEl("p", {text: "If you like this plugin and want to support its development, you can do so through my website by clicking this fancy image!"});
this.containerEl.createEl("a", {href: "https://ellpeck.de/support"}) this.containerEl.createEl("a", {href: "https://ellpeck.de/support"})

View file

@ -1,11 +1,13 @@
export const defaultSettings: SimpleTimeTrackerSettings = { export const defaultSettings: SimpleTimeTrackerSettings = {
timestampFormat: "YY-MM-DD hh:mm:ss", timestampFormat: "YY-MM-DD hh:mm:ss",
csvDelimiter: "," csvDelimiter: ",",
fineGrainedDurations: true
}; };
export interface SimpleTimeTrackerSettings { export interface SimpleTimeTrackerSettings {
timestampFormat: string; timestampFormat: string;
csvDelimiter: string; csvDelimiter: string;
fineGrainedDurations: boolean;
} }

View file

@ -1,4 +1,4 @@
import { moment, App, MarkdownSectionInformation, ButtonComponent, TextComponent } from "obsidian"; import { moment, App, MarkdownSectionInformation, ButtonComponent, TextComponent, TFile } from "obsidian";
import { SimpleTimeTrackerSettings } from "./settings"; import { SimpleTimeTrackerSettings } from "./settings";
export interface Tracker { export interface Tracker {
@ -12,8 +12,8 @@ export interface Entry {
subEntries: Entry[]; subEntries: Entry[];
} }
export async function saveTracker(tracker: Tracker, app: App, section: MarkdownSectionInformation): Promise<void> { export async function saveTracker(tracker: Tracker, app: App, fileName: string, section: MarkdownSectionInformation): Promise<void> {
let file = app.workspace.getActiveFile(); let file = app.vault.getAbstractFileByPath(fileName) as TFile;
if (!file) if (!file)
return; return;
let content = await app.vault.read(file); let content = await app.vault.read(file);
@ -39,7 +39,7 @@ export function loadTracker(json: string): Tracker {
return {entries: []}; return {entries: []};
} }
export function displayTracker(tracker: Tracker, element: HTMLElement, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings): void { export function displayTracker(tracker: Tracker, element: HTMLElement, file: string, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings): void {
// add start/stop controls // add start/stop controls
let running = isRunning(tracker); let running = isRunning(tracker);
let btn = new ButtonComponent(element) let btn = new ButtonComponent(element)
@ -52,7 +52,7 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, getSectio
} else { } else {
startNewEntry(tracker, newSegmentNameBox.getValue()); startNewEntry(tracker, newSegmentNameBox.getValue());
} }
await saveTracker(tracker, this.app, getSectionInfo()); await saveTracker(tracker, this.app, file, getSectionInfo());
}); });
btn.buttonEl.addClass("simple-time-tracker-btn"); btn.buttonEl.addClass("simple-time-tracker-btn");
let newSegmentNameBox = new TextComponent(element) let newSegmentNameBox = new TextComponent(element)
@ -80,7 +80,7 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, getSectio
createEl("th")); createEl("th"));
for (let entry of tracker.entries) for (let entry of tracker.entries)
addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, getSectionInfo, settings, 0); addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, file, getSectionInfo, settings, 0);
// add copy buttons // add copy buttons
let buttons = element.createEl("div", {cls: "simple-time-tracker-bottom"}); let buttons = element.createEl("div", {cls: "simple-time-tracker-bottom"});
@ -93,14 +93,14 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, getSectio
} }
setCountdownValues(tracker, current, total, currentDiv); setCountdownValues(tracker, current, total, currentDiv, settings);
let intervalId = window.setInterval(() => { let intervalId = window.setInterval(() => {
// we delete the interval timer when the element is removed // we delete the interval timer when the element is removed
if (!element.isConnected) { if (!element.isConnected) {
window.clearInterval(intervalId); window.clearInterval(intervalId);
return; return;
} }
setCountdownValues(tracker, current, total, currentDiv); setCountdownValues(tracker, current, total, currentDiv, settings);
}, 1000); }, 1000);
} }
@ -186,32 +186,38 @@ function getTotalDuration(entries: Entry[]): number {
return ret; return ret;
} }
function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLElement, currentDiv: HTMLDivElement) { function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLElement, currentDiv: HTMLDivElement, settings: SimpleTimeTrackerSettings) {
let running = getRunningEntry(tracker.entries); let running = getRunningEntry(tracker.entries);
if (running && !running.endTime) { if (running && !running.endTime) {
current.setText(formatDuration(getDuration(running))); current.setText(formatDuration(getDuration(running), settings));
currentDiv.hidden = false; currentDiv.hidden = false;
} else { } else {
currentDiv.hidden = true; currentDiv.hidden = true;
} }
total.setText(formatDuration(getTotalDuration(tracker.entries))); total.setText(formatDuration(getTotalDuration(tracker.entries), settings));
} }
function formatTimestamp(timestamp: number, settings: SimpleTimeTrackerSettings): string { function formatTimestamp(timestamp: number, settings: SimpleTimeTrackerSettings): string {
return moment.unix(timestamp).format(settings.timestampFormat); return moment.unix(timestamp).format(settings.timestampFormat);
} }
function formatDuration(totalTime: number): string { function formatDuration(totalTime: number, settings: SimpleTimeTrackerSettings): string {
let duration = moment.duration(totalTime);
let ret = ""; let ret = "";
let duration = moment.duration(totalTime);
let hours: number;
if (settings.fineGrainedDurations) {
if (duration.years() > 0) if (duration.years() > 0)
ret += duration.years() + "y "; ret += duration.years() + "y ";
if (duration.months() > 0) if (duration.months() > 0)
ret += duration.months() + "m "; ret += duration.months() + "M ";
if (duration.days() > 0) if (duration.days() > 0)
ret += duration.days() + "d "; ret += duration.days() + "d ";
if (duration.hours() > 0) hours = duration.hours();
ret += duration.hours() + "h "; } else {
hours = Math.floor(duration.asHours());
}
if (hours > 0)
ret += hours + "h ";
if (duration.minutes() > 0) if (duration.minutes() > 0)
ret += duration.minutes() + "m "; ret += duration.minutes() + "m ";
ret += duration.seconds() + "s"; ret += duration.seconds() + "s";
@ -222,7 +228,7 @@ function createMarkdownTable(tracker: Tracker, settings: SimpleTimeTrackerSettin
let table = [["Segment", "Start time", "End time", "Duration"]]; let table = [["Segment", "Start time", "End time", "Duration"]];
for (let entry of tracker.entries) for (let entry of tracker.entries)
table.push(...createTableSection(entry, settings)); table.push(...createTableSection(entry, settings));
table.push(["**Total**", "", "", `**${formatDuration(getTotalDuration(tracker.entries))}**`]); table.push(["**Total**", "", "", `**${formatDuration(getTotalDuration(tracker.entries), settings)}**`]);
let ret = ""; let ret = "";
// calculate the width every column needs to look neat when monospaced // calculate the width every column needs to look neat when monospaced
@ -230,12 +236,12 @@ function createMarkdownTable(tracker: Tracker, settings: SimpleTimeTrackerSettin
for (let r = 0; r < table.length; r++) { for (let r = 0; r < table.length; r++) {
// add separators after first row // add separators after first row
if (r == 1) if (r == 1)
ret += Array.from(Array(4).keys()).map(i => "-".repeat(widths[i])).join(" | ") + "\n"; ret += "| " + Array.from(Array(4).keys()).map(i => "-".repeat(widths[i])).join(" | ") + " |\n";
let row: string[] = []; let row: string[] = [];
for (let i = 0; i < 4; i++) for (let i = 0; i < 4; i++)
row.push(table[r][i].padEnd(widths[i], " ")); row.push(table[r][i].padEnd(widths[i], " "));
ret += row.join(" | ") + "\n"; ret += "| " + row.join(" | ") + " |\n";
} }
return ret; return ret;
} }
@ -254,7 +260,7 @@ function createTableSection(entry: Entry, settings: SimpleTimeTrackerSettings):
entry.name, entry.name,
entry.startTime ? formatTimestamp(entry.startTime, settings) : "", entry.startTime ? formatTimestamp(entry.startTime, settings) : "",
entry.endTime ? formatTimestamp(entry.endTime, settings) : "", entry.endTime ? formatTimestamp(entry.endTime, settings) : "",
entry.endTime || entry.subEntries ? formatDuration(getDuration(entry)) : ""]]; entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : ""]];
if (entry.subEntries) { if (entry.subEntries) {
for (let sub of entry.subEntries) for (let sub of entry.subEntries)
ret.push(...createTableSection(sub, settings)); ret.push(...createTableSection(sub, settings));
@ -262,7 +268,7 @@ function createTableSection(entry: Entry, settings: SimpleTimeTrackerSettings):
return ret; return ret;
} }
function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableElement, newSegmentNameBox: TextComponent, running: boolean, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings, indent: number) { function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableElement, newSegmentNameBox: TextComponent, running: boolean, file: string, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings, indent: number) {
let row = table.createEl("tr"); let row = table.createEl("tr");
let name = row.createEl("td"); let name = row.createEl("td");
@ -273,7 +279,7 @@ function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableEle
row.createEl("td", {text: entry.startTime ? formatTimestamp(entry.startTime, settings) : ""}); row.createEl("td", {text: entry.startTime ? formatTimestamp(entry.startTime, settings) : ""});
row.createEl("td", {text: entry.endTime ? formatTimestamp(entry.endTime, settings) : ""}); row.createEl("td", {text: entry.endTime ? formatTimestamp(entry.endTime, settings) : ""});
row.createEl("td", {text: entry.endTime || entry.subEntries ? formatDuration(getDuration(entry)) : ""}); row.createEl("td", {text: entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : ""});
let entryButtons = row.createEl("td"); let entryButtons = row.createEl("td");
if (!running) { if (!running) {
@ -283,7 +289,7 @@ function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableEle
.setTooltip("Continue") .setTooltip("Continue")
.onClick(async () => { .onClick(async () => {
startSubEntry(entry, newSegmentNameBox.getValue()); startSubEntry(entry, newSegmentNameBox.getValue());
await saveTracker(tracker, this.app, getSectionInfo()); await saveTracker(tracker, this.app, file, getSectionInfo());
}); });
} }
let editButton = new ButtonComponent(entryButtons) let editButton = new ButtonComponent(entryButtons)
@ -298,7 +304,7 @@ function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableEle
if (nameBox.getValue()) { if (nameBox.getValue()) {
entry.name = nameBox.getValue(); entry.name = nameBox.getValue();
namePar.setText(entry.name); namePar.setText(entry.name);
await saveTracker(tracker, this.app, getSectionInfo()); await saveTracker(tracker, this.app, file, getSectionInfo());
} }
} else { } else {
namePar.hidden = true; namePar.hidden = true;
@ -313,11 +319,11 @@ function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableEle
.setIcon("lucide-trash") .setIcon("lucide-trash")
.onClick(async () => { .onClick(async () => {
removeEntry(tracker.entries, entry); removeEntry(tracker.entries, entry);
await saveTracker(tracker, this.app, getSectionInfo()); await saveTracker(tracker, this.app, file, getSectionInfo());
}); });
if (entry.subEntries) { if (entry.subEntries) {
for (let sub of entry.subEntries) for (let sub of entry.subEntries)
addEditableTableRow(tracker, sub, table, newSegmentNameBox, running, getSectionInfo, settings, indent + 1); addEditableTableRow(tracker, sub, table, newSegmentNameBox, running, file, getSectionInfo, settings, indent + 1);
} }
} }

View file

@ -0,0 +1,29 @@
{
"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
}

View file

@ -4,6 +4,7 @@
"switcher", "switcher",
"graph", "graph",
"backlink", "backlink",
"canvas",
"outgoing-link", "outgoing-link",
"tag-pane", "tag-pane",
"page-preview", "page-preview",
@ -12,7 +13,7 @@
"note-composer", "note-composer",
"command-palette", "command-palette",
"editor-status", "editor-status",
"starred", "bookmarks",
"outline", "outline",
"word-count", "word-count",
"file-recovery" "file-recovery"

View file

@ -1,4 +1,5 @@
{ {
"timestampFormat": "YY-MM-DD hh:mm:ss", "timestampFormat": "YY-MM-DD hh:mm:ss",
"csvDelimiter": "," "csvDelimiter": ",",
"fineGrainedDurations": false
} }

View file

@ -0,0 +1,404 @@
/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
var __create = Object.create;
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
var __export = (target, all) => {
__markAsModule(target);
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __reExport = (target, module2, desc) => {
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
for (let key of __getOwnPropNames(module2))
if (!__hasOwnProp.call(target, key) && key !== "default")
__defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
}
return target;
};
var __toModule = (module2) => {
return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
};
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/main.ts
__export(exports, {
default: () => SimpleTimeTrackerPlugin
});
var import_obsidian3 = __toModule(require("obsidian"));
// src/settings.ts
var defaultSettings = {
timestampFormat: "YY-MM-DD hh:mm:ss",
csvDelimiter: ",",
fineGrainedDurations: true
};
// src/settings-tab.ts
var import_obsidian = __toModule(require("obsidian"));
var SimpleTimeTrackerSettingsTab = class extends import_obsidian.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
this.containerEl.empty();
this.containerEl.createEl("h2", { text: "Super Simple Time Tracker Settings" });
new import_obsidian.Setting(this.containerEl).setName("Timestamp Display Format").setDesc(createFragment((f) => {
f.createSpan({ text: "The way that timestamps in time tracker tables should be displayed. Uses " });
f.createEl("a", { text: "moment.js", href: "https://momentjs.com/docs/#/parsing/string-format/" });
f.createSpan({ text: " syntax." });
})).addText((t) => {
t.setValue(String(this.plugin.settings.timestampFormat));
t.onChange((v) => __async(this, null, function* () {
this.plugin.settings.timestampFormat = v.length ? v : defaultSettings.timestampFormat;
yield this.plugin.saveSettings();
}));
});
new import_obsidian.Setting(this.containerEl).setName("CSV Delimiter").setDesc("The delimiter character that should be used when copying a tracker table as CSV. For example, some languages use a semicolon instead of a comma.").addText((t) => {
t.setValue(String(this.plugin.settings.csvDelimiter));
t.onChange((v) => __async(this, null, function* () {
this.plugin.settings.csvDelimiter = v.length ? v : defaultSettings.csvDelimiter;
yield this.plugin.saveSettings();
}));
});
new import_obsidian.Setting(this.containerEl).setName("Fine-Grained Durations").setDesc("Whether durations should include days, months and years. If this is disabled, additional time units will be displayed as part of the hours.").addToggle((t) => {
t.setValue(this.plugin.settings.fineGrainedDurations);
t.onChange((v) => __async(this, null, function* () {
this.plugin.settings.fineGrainedDurations = v;
yield this.plugin.saveSettings();
}));
});
this.containerEl.createEl("hr");
this.containerEl.createEl("p", { text: "If you like this plugin and want to support its development, you can do so through my website by clicking this fancy image!" });
this.containerEl.createEl("a", { href: "https://ellpeck.de/support" }).createEl("img", {
attr: { src: "https://ellpeck.de/res/generalsupport.png" },
cls: "simple-time-tracker-support"
});
}
};
// src/tracker.ts
var import_obsidian2 = __toModule(require("obsidian"));
function saveTracker(tracker, app, fileName, section) {
return __async(this, null, function* () {
let file = app.vault.getAbstractFileByPath(fileName);
if (!file)
return;
let content = yield app.vault.read(file);
let lines = content.split("\n");
let prev = lines.filter((_, i) => i <= section.lineStart).join("\n");
let next = lines.filter((_, i) => i >= section.lineEnd).join("\n");
content = `${prev}
${JSON.stringify(tracker)}
${next}`;
yield app.vault.modify(file, content);
});
}
function loadTracker(json) {
if (json) {
try {
return JSON.parse(json);
} catch (e) {
console.log(`Failed to parse Tracker from ${json}`);
}
}
return { entries: [] };
}
function displayTracker(tracker, element, file, getSectionInfo, settings) {
let running = isRunning(tracker);
let btn = new import_obsidian2.ButtonComponent(element).setClass("clickable-icon").setIcon(`lucide-${running ? "stop" : "play"}-circle`).setTooltip(running ? "End" : "Start").onClick(() => __async(this, null, function* () {
if (running) {
endRunningEntry(tracker);
} else {
startNewEntry(tracker, newSegmentNameBox.getValue());
}
yield saveTracker(tracker, this.app, file, getSectionInfo());
}));
btn.buttonEl.addClass("simple-time-tracker-btn");
let newSegmentNameBox = new import_obsidian2.TextComponent(element).setPlaceholder("Segment name").setDisabled(running);
newSegmentNameBox.inputEl.addClass("simple-time-tracker-txt");
let timer = element.createDiv({ cls: "simple-time-tracker-timers" });
let currentDiv = timer.createEl("div", { cls: "simple-time-tracker-timer" });
let current = currentDiv.createEl("span", { cls: "simple-time-tracker-timer-time" });
currentDiv.createEl("span", { text: "Current" });
let totalDiv = timer.createEl("div", { cls: "simple-time-tracker-timer" });
let total = totalDiv.createEl("span", { cls: "simple-time-tracker-timer-time", text: "0s" });
totalDiv.createEl("span", { text: "Total" });
if (tracker.entries.length > 0) {
let table = element.createEl("table", { cls: "simple-time-tracker-table" });
table.createEl("tr").append(createEl("th", { text: "Segment" }), createEl("th", { text: "Start time" }), createEl("th", { text: "End time" }), createEl("th", { text: "Duration" }), createEl("th"));
for (let entry of tracker.entries)
addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, file, getSectionInfo, settings, 0);
let buttons = element.createEl("div", { cls: "simple-time-tracker-bottom" });
new import_obsidian2.ButtonComponent(buttons).setButtonText("Copy as table").onClick(() => navigator.clipboard.writeText(createMarkdownTable(tracker, settings)));
new import_obsidian2.ButtonComponent(buttons).setButtonText("Copy as CSV").onClick(() => navigator.clipboard.writeText(createCsv(tracker, settings)));
}
setCountdownValues(tracker, current, total, currentDiv, settings);
let intervalId = window.setInterval(() => {
if (!element.isConnected) {
window.clearInterval(intervalId);
return;
}
setCountdownValues(tracker, current, total, currentDiv, settings);
}, 1e3);
}
function startSubEntry(entry, name) {
if (!entry.subEntries) {
entry.subEntries = [__spreadProps(__spreadValues({}, entry), { name: `Part 1` })];
entry.startTime = null;
entry.endTime = null;
}
if (!name)
name = `Part ${entry.subEntries.length + 1}`;
entry.subEntries.push({ name, startTime: (0, import_obsidian2.moment)().unix(), endTime: null, subEntries: null });
}
function startNewEntry(tracker, name) {
if (!name)
name = `Segment ${tracker.entries.length + 1}`;
let entry = { name, startTime: (0, import_obsidian2.moment)().unix(), endTime: null, subEntries: null };
tracker.entries.push(entry);
}
function endRunningEntry(tracker) {
let entry = getRunningEntry(tracker.entries);
entry.endTime = (0, import_obsidian2.moment)().unix();
}
function removeEntry(entries, toRemove) {
if (entries.contains(toRemove)) {
entries.remove(toRemove);
return true;
} else {
for (let entry of entries) {
if (entry.subEntries && removeEntry(entry.subEntries, toRemove)) {
if (entry.subEntries.length == 1) {
let single = entry.subEntries[0];
entry.startTime = single.startTime;
entry.endTime = single.endTime;
entry.subEntries = null;
}
return true;
}
}
}
return false;
}
function isRunning(tracker) {
return !!getRunningEntry(tracker.entries);
}
function getRunningEntry(entries) {
for (let entry of entries) {
if (entry.subEntries) {
let running = getRunningEntry(entry.subEntries);
if (running)
return running;
} else {
if (!entry.endTime)
return entry;
}
}
return null;
}
function getDuration(entry) {
if (entry.subEntries) {
return getTotalDuration(entry.subEntries);
} else {
let endTime = entry.endTime ? import_obsidian2.moment.unix(entry.endTime) : (0, import_obsidian2.moment)();
return endTime.diff(import_obsidian2.moment.unix(entry.startTime));
}
}
function getTotalDuration(entries) {
let ret = 0;
for (let entry of entries)
ret += getDuration(entry);
return ret;
}
function setCountdownValues(tracker, current, total, currentDiv, settings) {
let running = getRunningEntry(tracker.entries);
if (running && !running.endTime) {
current.setText(formatDuration(getDuration(running), settings));
currentDiv.hidden = false;
} else {
currentDiv.hidden = true;
}
total.setText(formatDuration(getTotalDuration(tracker.entries), settings));
}
function formatTimestamp(timestamp, settings) {
return import_obsidian2.moment.unix(timestamp).format(settings.timestampFormat);
}
function formatDuration(totalTime, settings) {
let ret = "";
let duration = import_obsidian2.moment.duration(totalTime);
let hours;
if (settings.fineGrainedDurations) {
if (duration.years() > 0)
ret += duration.years() + "y ";
if (duration.months() > 0)
ret += duration.months() + "M ";
if (duration.days() > 0)
ret += duration.days() + "d ";
hours = duration.hours();
} else {
hours = Math.floor(duration.asHours());
}
if (hours > 0)
ret += hours + "h ";
if (duration.minutes() > 0)
ret += duration.minutes() + "m ";
ret += duration.seconds() + "s";
return ret;
}
function createMarkdownTable(tracker, settings) {
let table = [["Segment", "Start time", "End time", "Duration"]];
for (let entry of tracker.entries)
table.push(...createTableSection(entry, settings));
table.push(["**Total**", "", "", `**${formatDuration(getTotalDuration(tracker.entries), settings)}**`]);
let ret = "";
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++) {
if (r == 1)
ret += "| " + Array.from(Array(4).keys()).map((i) => "-".repeat(widths[i])).join(" | ") + " |\n";
let row = [];
for (let i = 0; i < 4; i++)
row.push(table[r][i].padEnd(widths[i], " "));
ret += "| " + row.join(" | ") + " |\n";
}
return ret;
}
function createCsv(tracker, settings) {
let ret = "";
for (let entry of tracker.entries) {
for (let row of createTableSection(entry, settings))
ret += row.join(settings.csvDelimiter) + "\n";
}
return ret;
}
function createTableSection(entry, settings) {
let ret = [[
entry.name,
entry.startTime ? formatTimestamp(entry.startTime, settings) : "",
entry.endTime ? formatTimestamp(entry.endTime, settings) : "",
entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : ""
]];
if (entry.subEntries) {
for (let sub of entry.subEntries)
ret.push(...createTableSection(sub, settings));
}
return ret;
}
function addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, file, getSectionInfo, settings, indent) {
let row = table.createEl("tr");
let name = row.createEl("td");
let namePar = name.createEl("span", { text: entry.name });
namePar.style.marginLeft = `${indent}em`;
let nameBox = new import_obsidian2.TextComponent(name).setValue(entry.name);
nameBox.inputEl.hidden = true;
row.createEl("td", { text: entry.startTime ? formatTimestamp(entry.startTime, settings) : "" });
row.createEl("td", { text: entry.endTime ? formatTimestamp(entry.endTime, settings) : "" });
row.createEl("td", { text: entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : "" });
let entryButtons = row.createEl("td");
if (!running) {
new import_obsidian2.ButtonComponent(entryButtons).setClass("clickable-icon").setIcon(`lucide-play`).setTooltip("Continue").onClick(() => __async(this, null, function* () {
startSubEntry(entry, newSegmentNameBox.getValue());
yield saveTracker(tracker, this.app, file, getSectionInfo());
}));
}
let editButton = new import_obsidian2.ButtonComponent(entryButtons).setClass("clickable-icon").setTooltip("Edit").setIcon("lucide-pencil").onClick(() => __async(this, null, function* () {
if (namePar.hidden) {
namePar.hidden = false;
nameBox.inputEl.hidden = true;
editButton.setIcon("lucide-pencil");
if (nameBox.getValue()) {
entry.name = nameBox.getValue();
namePar.setText(entry.name);
yield saveTracker(tracker, this.app, file, getSectionInfo());
}
} else {
namePar.hidden = true;
nameBox.inputEl.hidden = false;
nameBox.setValue(entry.name);
editButton.setIcon("lucide-check");
}
}));
new import_obsidian2.ButtonComponent(entryButtons).setClass("clickable-icon").setTooltip("Remove").setIcon("lucide-trash").onClick(() => __async(this, null, function* () {
removeEntry(tracker.entries, entry);
yield saveTracker(tracker, this.app, file, getSectionInfo());
}));
if (entry.subEntries) {
for (let sub of entry.subEntries)
addEditableTableRow(tracker, sub, table, newSegmentNameBox, running, file, getSectionInfo, settings, indent + 1);
}
}
// src/main.ts
var SimpleTimeTrackerPlugin = class extends import_obsidian3.Plugin {
onload() {
return __async(this, null, function* () {
yield this.loadSettings();
this.addSettingTab(new SimpleTimeTrackerSettingsTab(this.app, this));
this.registerMarkdownCodeBlockProcessor("simple-time-tracker", (s, e, i) => {
let tracker = loadTracker(s);
e.empty();
displayTracker(tracker, e, i.sourcePath, () => i.getSectionInfo(e), this.settings);
});
this.addCommand({
id: `insert`,
name: `Insert Time Tracker`,
editorCallback: (e, _) => {
e.replaceSelection("```simple-time-tracker\n```\n");
}
});
});
}
loadSettings() {
return __async(this, null, function* () {
this.settings = Object.assign({}, defaultSettings, yield this.loadData());
});
}
saveSettings() {
return __async(this, null, function* () {
yield this.saveData(this.settings);
});
}
};

View file

@ -72,7 +72,8 @@ var import_obsidian3 = __toModule(require("obsidian"));
// src/settings.ts // src/settings.ts
var defaultSettings = { var defaultSettings = {
timestampFormat: "YY-MM-DD hh:mm:ss", timestampFormat: "YY-MM-DD hh:mm:ss",
csvDelimiter: "," csvDelimiter: ",",
fineGrainedDurations: true
}; };
// src/settings-tab.ts // src/settings-tab.ts
@ -103,17 +104,27 @@ var SimpleTimeTrackerSettingsTab = class extends import_obsidian.PluginSettingTa
yield this.plugin.saveSettings(); yield this.plugin.saveSettings();
})); }));
}); });
new import_obsidian.Setting(this.containerEl).setName("Fine-Grained Durations").setDesc("Whether durations should include days, months and years. If this is disabled, additional time units will be displayed as part of the hours.").addToggle((t) => {
t.setValue(this.plugin.settings.fineGrainedDurations);
t.onChange((v) => __async(this, null, function* () {
this.plugin.settings.fineGrainedDurations = v;
yield this.plugin.saveSettings();
}));
});
this.containerEl.createEl("hr"); this.containerEl.createEl("hr");
this.containerEl.createEl("p", { text: "If you like this plugin and want to support its development, you can do so through my website by clicking this fancy image!" }); this.containerEl.createEl("p", { text: "If you like this plugin and want to support its development, you can do so through my website by clicking this fancy image!" });
this.containerEl.createEl("a", { href: "https://ellpeck.de/support" }).createEl("img", { attr: { src: "https://ellpeck.de/res/generalsupport.png" }, cls: "simple-time-tracker-support" }); this.containerEl.createEl("a", { href: "https://ellpeck.de/support" }).createEl("img", {
attr: { src: "https://ellpeck.de/res/generalsupport.png" },
cls: "simple-time-tracker-support"
});
} }
}; };
// src/tracker.ts // src/tracker.ts
var import_obsidian2 = __toModule(require("obsidian")); var import_obsidian2 = __toModule(require("obsidian"));
function saveTracker(tracker, app, section) { function saveTracker(tracker, app, fileName, section) {
return __async(this, null, function* () { return __async(this, null, function* () {
let file = app.workspace.getActiveFile(); let file = app.vault.getAbstractFileByPath(fileName);
if (!file) if (!file)
return; return;
let content = yield app.vault.read(file); let content = yield app.vault.read(file);
@ -136,7 +147,7 @@ function loadTracker(json) {
} }
return { entries: [] }; return { entries: [] };
} }
function displayTracker(tracker, element, getSectionInfo, settings) { function displayTracker(tracker, element, file, getSectionInfo, settings) {
let running = isRunning(tracker); let running = isRunning(tracker);
let btn = new import_obsidian2.ButtonComponent(element).setClass("clickable-icon").setIcon(`lucide-${running ? "stop" : "play"}-circle`).setTooltip(running ? "End" : "Start").onClick(() => __async(this, null, function* () { let btn = new import_obsidian2.ButtonComponent(element).setClass("clickable-icon").setIcon(`lucide-${running ? "stop" : "play"}-circle`).setTooltip(running ? "End" : "Start").onClick(() => __async(this, null, function* () {
if (running) { if (running) {
@ -144,7 +155,7 @@ function displayTracker(tracker, element, getSectionInfo, settings) {
} else { } else {
startNewEntry(tracker, newSegmentNameBox.getValue()); startNewEntry(tracker, newSegmentNameBox.getValue());
} }
yield saveTracker(tracker, this.app, getSectionInfo()); yield saveTracker(tracker, this.app, file, getSectionInfo());
})); }));
btn.buttonEl.addClass("simple-time-tracker-btn"); btn.buttonEl.addClass("simple-time-tracker-btn");
let newSegmentNameBox = new import_obsidian2.TextComponent(element).setPlaceholder("Segment name").setDisabled(running); let newSegmentNameBox = new import_obsidian2.TextComponent(element).setPlaceholder("Segment name").setDisabled(running);
@ -160,18 +171,18 @@ function displayTracker(tracker, element, getSectionInfo, settings) {
let table = element.createEl("table", { cls: "simple-time-tracker-table" }); let table = element.createEl("table", { cls: "simple-time-tracker-table" });
table.createEl("tr").append(createEl("th", { text: "Segment" }), createEl("th", { text: "Start time" }), createEl("th", { text: "End time" }), createEl("th", { text: "Duration" }), createEl("th")); table.createEl("tr").append(createEl("th", { text: "Segment" }), createEl("th", { text: "Start time" }), createEl("th", { text: "End time" }), createEl("th", { text: "Duration" }), createEl("th"));
for (let entry of tracker.entries) for (let entry of tracker.entries)
addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, getSectionInfo, settings, 0); addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, file, getSectionInfo, settings, 0);
let buttons = element.createEl("div", { cls: "simple-time-tracker-bottom" }); let buttons = element.createEl("div", { cls: "simple-time-tracker-bottom" });
new import_obsidian2.ButtonComponent(buttons).setButtonText("Copy as table").onClick(() => navigator.clipboard.writeText(createMarkdownTable(tracker, settings))); new import_obsidian2.ButtonComponent(buttons).setButtonText("Copy as table").onClick(() => navigator.clipboard.writeText(createMarkdownTable(tracker, settings)));
new import_obsidian2.ButtonComponent(buttons).setButtonText("Copy as CSV").onClick(() => navigator.clipboard.writeText(createCsv(tracker, settings))); new import_obsidian2.ButtonComponent(buttons).setButtonText("Copy as CSV").onClick(() => navigator.clipboard.writeText(createCsv(tracker, settings)));
} }
setCountdownValues(tracker, current, total, currentDiv); setCountdownValues(tracker, current, total, currentDiv, settings);
let intervalId = window.setInterval(() => { let intervalId = window.setInterval(() => {
if (!element.isConnected) { if (!element.isConnected) {
window.clearInterval(intervalId); window.clearInterval(intervalId);
return; return;
} }
setCountdownValues(tracker, current, total, currentDiv); setCountdownValues(tracker, current, total, currentDiv, settings);
}, 1e3); }, 1e3);
} }
function startSubEntry(entry, name) { function startSubEntry(entry, name) {
@ -243,30 +254,36 @@ function getTotalDuration(entries) {
ret += getDuration(entry); ret += getDuration(entry);
return ret; return ret;
} }
function setCountdownValues(tracker, current, total, currentDiv) { function setCountdownValues(tracker, current, total, currentDiv, settings) {
let running = getRunningEntry(tracker.entries); let running = getRunningEntry(tracker.entries);
if (running && !running.endTime) { if (running && !running.endTime) {
current.setText(formatDuration(getDuration(running))); current.setText(formatDuration(getDuration(running), settings));
currentDiv.hidden = false; currentDiv.hidden = false;
} else { } else {
currentDiv.hidden = true; currentDiv.hidden = true;
} }
total.setText(formatDuration(getTotalDuration(tracker.entries))); total.setText(formatDuration(getTotalDuration(tracker.entries), settings));
} }
function formatTimestamp(timestamp, settings) { function formatTimestamp(timestamp, settings) {
return import_obsidian2.moment.unix(timestamp).format(settings.timestampFormat); return import_obsidian2.moment.unix(timestamp).format(settings.timestampFormat);
} }
function formatDuration(totalTime) { function formatDuration(totalTime, settings) {
let duration = import_obsidian2.moment.duration(totalTime);
let ret = ""; let ret = "";
let duration = import_obsidian2.moment.duration(totalTime);
let hours;
if (settings.fineGrainedDurations) {
if (duration.years() > 0) if (duration.years() > 0)
ret += duration.years() + "y "; ret += duration.years() + "y ";
if (duration.months() > 0) if (duration.months() > 0)
ret += duration.months() + "m "; ret += duration.months() + "M ";
if (duration.days() > 0) if (duration.days() > 0)
ret += duration.days() + "d "; ret += duration.days() + "d ";
if (duration.hours() > 0) hours = duration.hours();
ret += duration.hours() + "h "; } else {
hours = Math.floor(duration.asHours());
}
if (hours > 0)
ret += hours + "h ";
if (duration.minutes() > 0) if (duration.minutes() > 0)
ret += duration.minutes() + "m "; ret += duration.minutes() + "m ";
ret += duration.seconds() + "s"; ret += duration.seconds() + "s";
@ -276,16 +293,16 @@ function createMarkdownTable(tracker, settings) {
let table = [["Segment", "Start time", "End time", "Duration"]]; let table = [["Segment", "Start time", "End time", "Duration"]];
for (let entry of tracker.entries) for (let entry of tracker.entries)
table.push(...createTableSection(entry, settings)); table.push(...createTableSection(entry, settings));
table.push(["**Total**", "", "", `**${formatDuration(getTotalDuration(tracker.entries))}**`]); table.push(["**Total**", "", "", `**${formatDuration(getTotalDuration(tracker.entries), settings)}**`]);
let ret = ""; let ret = "";
let widths = Array.from(Array(4).keys()).map((i) => Math.max(...table.map((a) => a[i].length))); 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++) { for (let r = 0; r < table.length; r++) {
if (r == 1) if (r == 1)
ret += Array.from(Array(4).keys()).map((i) => "-".repeat(widths[i])).join(" | ") + "\n"; ret += "| " + Array.from(Array(4).keys()).map((i) => "-".repeat(widths[i])).join(" | ") + " |\n";
let row = []; let row = [];
for (let i = 0; i < 4; i++) for (let i = 0; i < 4; i++)
row.push(table[r][i].padEnd(widths[i], " ")); row.push(table[r][i].padEnd(widths[i], " "));
ret += row.join(" | ") + "\n"; ret += "| " + row.join(" | ") + " |\n";
} }
return ret; return ret;
} }
@ -302,7 +319,7 @@ function createTableSection(entry, settings) {
entry.name, entry.name,
entry.startTime ? formatTimestamp(entry.startTime, settings) : "", entry.startTime ? formatTimestamp(entry.startTime, settings) : "",
entry.endTime ? formatTimestamp(entry.endTime, settings) : "", entry.endTime ? formatTimestamp(entry.endTime, settings) : "",
entry.endTime || entry.subEntries ? formatDuration(getDuration(entry)) : "" entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : ""
]]; ]];
if (entry.subEntries) { if (entry.subEntries) {
for (let sub of entry.subEntries) for (let sub of entry.subEntries)
@ -310,7 +327,7 @@ function createTableSection(entry, settings) {
} }
return ret; return ret;
} }
function addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, getSectionInfo, settings, indent) { function addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, file, getSectionInfo, settings, indent) {
let row = table.createEl("tr"); let row = table.createEl("tr");
let name = row.createEl("td"); let name = row.createEl("td");
let namePar = name.createEl("span", { text: entry.name }); let namePar = name.createEl("span", { text: entry.name });
@ -319,12 +336,12 @@ function addEditableTableRow(tracker, entry, table, newSegmentNameBox, running,
nameBox.inputEl.hidden = true; nameBox.inputEl.hidden = true;
row.createEl("td", { text: entry.startTime ? formatTimestamp(entry.startTime, settings) : "" }); row.createEl("td", { text: entry.startTime ? formatTimestamp(entry.startTime, settings) : "" });
row.createEl("td", { text: entry.endTime ? formatTimestamp(entry.endTime, settings) : "" }); row.createEl("td", { text: entry.endTime ? formatTimestamp(entry.endTime, settings) : "" });
row.createEl("td", { text: entry.endTime || entry.subEntries ? formatDuration(getDuration(entry)) : "" }); row.createEl("td", { text: entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : "" });
let entryButtons = row.createEl("td"); let entryButtons = row.createEl("td");
if (!running) { if (!running) {
new import_obsidian2.ButtonComponent(entryButtons).setClass("clickable-icon").setIcon(`lucide-play`).setTooltip("Continue").onClick(() => __async(this, null, function* () { new import_obsidian2.ButtonComponent(entryButtons).setClass("clickable-icon").setIcon(`lucide-play`).setTooltip("Continue").onClick(() => __async(this, null, function* () {
startSubEntry(entry, newSegmentNameBox.getValue()); startSubEntry(entry, newSegmentNameBox.getValue());
yield saveTracker(tracker, this.app, getSectionInfo()); yield saveTracker(tracker, this.app, file, getSectionInfo());
})); }));
} }
let editButton = new import_obsidian2.ButtonComponent(entryButtons).setClass("clickable-icon").setTooltip("Edit").setIcon("lucide-pencil").onClick(() => __async(this, null, function* () { let editButton = new import_obsidian2.ButtonComponent(entryButtons).setClass("clickable-icon").setTooltip("Edit").setIcon("lucide-pencil").onClick(() => __async(this, null, function* () {
@ -335,7 +352,7 @@ function addEditableTableRow(tracker, entry, table, newSegmentNameBox, running,
if (nameBox.getValue()) { if (nameBox.getValue()) {
entry.name = nameBox.getValue(); entry.name = nameBox.getValue();
namePar.setText(entry.name); namePar.setText(entry.name);
yield saveTracker(tracker, this.app, getSectionInfo()); yield saveTracker(tracker, this.app, file, getSectionInfo());
} }
} else { } else {
namePar.hidden = true; namePar.hidden = true;
@ -346,11 +363,11 @@ function addEditableTableRow(tracker, entry, table, newSegmentNameBox, running,
})); }));
new import_obsidian2.ButtonComponent(entryButtons).setClass("clickable-icon").setTooltip("Remove").setIcon("lucide-trash").onClick(() => __async(this, null, function* () { new import_obsidian2.ButtonComponent(entryButtons).setClass("clickable-icon").setTooltip("Remove").setIcon("lucide-trash").onClick(() => __async(this, null, function* () {
removeEntry(tracker.entries, entry); removeEntry(tracker.entries, entry);
yield saveTracker(tracker, this.app, getSectionInfo()); yield saveTracker(tracker, this.app, file, getSectionInfo());
})); }));
if (entry.subEntries) { if (entry.subEntries) {
for (let sub of entry.subEntries) for (let sub of entry.subEntries)
addEditableTableRow(tracker, sub, table, newSegmentNameBox, running, getSectionInfo, settings, indent + 1); addEditableTableRow(tracker, sub, table, newSegmentNameBox, running, file, getSectionInfo, settings, indent + 1);
} }
} }
@ -363,7 +380,7 @@ var SimpleTimeTrackerPlugin = class extends import_obsidian3.Plugin {
this.registerMarkdownCodeBlockProcessor("simple-time-tracker", (s, e, i) => { this.registerMarkdownCodeBlockProcessor("simple-time-tracker", (s, e, i) => {
let tracker = loadTracker(s); let tracker = loadTracker(s);
e.empty(); e.empty();
displayTracker(tracker, e, () => i.getSectionInfo(e), this.settings); displayTracker(tracker, e, i.sourcePath, () => i.getSectionInfo(e), this.settings);
}); });
this.addCommand({ this.addCommand({
id: `insert`, id: `insert`,

View file

@ -0,0 +1,10 @@
{
"id": "simple-time-tracker",
"name": "Super Simple Time Tracker",
"version": "0.1.7",
"minAppVersion": "1.2.8",
"description": "Multi-purpose time trackers for your notes!",
"author": "Ellpeck",
"authorUrl": "https://ellpeck.de",
"isDesktopOnly": false
}

View file

@ -1,8 +1,8 @@
{ {
"id": "simple-time-tracker", "id": "simple-time-tracker",
"name": "Super Simple Time Tracker", "name": "Super Simple Time Tracker",
"version": "0.1.6", "version": "0.1.7",
"minAppVersion": "0.15.0", "minAppVersion": "1.2.8",
"description": "Multi-purpose time trackers for your notes!", "description": "Multi-purpose time trackers for your notes!",
"author": "Ellpeck", "author": "Ellpeck",
"authorUrl": "https://ellpeck.de", "authorUrl": "https://ellpeck.de",

View file

@ -0,0 +1,64 @@
.simple-time-tracker-support {
max-width: 50%;
width: 400px;
height: auto;
}
.simple-time-tracker-btn,
.simple-time-tracker-txt {
display: block;
margin-left: auto;
margin-right: auto;
}
.simple-time-tracker-txt {
text-align: center;
}
.simple-time-tracker-btn {
margin-top: 10px;
margin-bottom: 10px;
}
.simple-time-tracker-btn svg {
width: 32px;
height: 32px;
}
.simple-time-tracker-bottom button {
margin: 10px 5px 10px 5px;
}
.simple-time-tracker-timers,
.simple-time-tracker-bottom {
display: flex;
justify-content: center;
text-align: center;
}
.simple-time-tracker-timers span {
display: block;
}
.simple-time-tracker-timer {
margin: 20px;
}
.simple-time-tracker-timer-time {
font-size: xx-large;
font-weight: bolder;
}
.simple-time-tracker-table {
width: 100%;
margin-top: 20px;
}
.simple-time-tracker-table td,
.simple-time-tracker-table th {
border: none;
}
.simple-time-tracker-table .clickable-icon {
display: inline;
}

View file

@ -2,10 +2,5 @@
More notes for my cool project! This note shows that we can correctly display accumulated time that lasts longer than a 24 hour day! More notes for my cool project! This note shows that we can correctly display accumulated time that lasts longer than a 24 hour day!
```simple-time-tracker ```simple-time-tracker
{"entries":[ {"entries":[{"name":"test","startTime":1596265200,"endTime":1627887600,"subEntries":null},{"name":"test","startTime":1627801200,"endTime":1633158000,"subEntries":null},{"name":"test","startTime":1659337200,"endTime":1659423600,"subEntries":null},{"name":"test","startTime":1664627410,"endTime":1664631605,"subEntries":null},{"name":"Segment 5","startTime":1684858616,"endTime":1684858619,"subEntries":null}]}
{"name":"test","startTime":1596265200,"endTime":1627887600,"subEntries":null},
{"name":"test","startTime":1627801200,"endTime":1633158000,"subEntries":null},
{"name":"test","startTime":1659337200,"endTime":1659423600,"subEntries":null},
{"name":"test","startTime":1664627410,"endTime":1664631605,"subEntries":null}
]}
``` ```

View file

@ -2,14 +2,5 @@
These are the notes for my cool project. There's so much left to do! I wish I had a way to track the amount of time I spend on each part of the project. These are the notes for my cool project. There's so much left to do! I wish I had a way to track the amount of time I spend on each part of the project.
```simple-time-tracker ```simple-time-tracker
{"entries":[{"name":"Segment 1","startTime":1666189948,"endTime":1666189951,"subEntries":null},{"name":"Segment 2","startTime":1666189953,"endTime":1666189961,"subEntries":null},{"name":"Segment 3","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":1666189962,"endTime":1666189995,"subEntries":null},{"name":"Part 2","startTime":1666190004,"endTime":1666190025,"subEntries":null},{"name":"Part 3","startTime":1666190094,"endTime":1666190101,"subEntries":null}]},{"name":"Segment 4","startTime":1666190088,"endTime":1666190091,"subEntries":null}]} {"entries":[{"name":"Segment 1","startTime":1666189948,"endTime":1666189951,"subEntries":null},{"name":"Segment 2","startTime":1666189953,"endTime":1666189961,"subEntries":null},{"name":"Segment 3","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":1666189962,"endTime":1666189995,"subEntries":null},{"name":"Part 2","startTime":1666190004,"endTime":1666190025,"subEntries":null},{"name":"Part 3","startTime":1666190094,"endTime":1666190101,"subEntries":null}]},{"name":"Segment 4","startTime":1666190088,"endTime":1666190091,"subEntries":null},{"name":"Segment 5","startTime":1684857704,"endTime":1684857708,"subEntries":null},{"name":"Segment 6","startTime":1684857710,"endTime":1684857712,"subEntries":null},{"name":"Segment 7","startTime":1684857729,"endTime":1684857732,"subEntries":null},{"name":"Segment 8","startTime":1684857743,"endTime":1684857748,"subEntries":null}]}
```
```
Think about project,22-09-27 09:09:37,22-09-27 09:59:48,50m 11s
Create project note,22-09-27 10:00:10,22-09-27 10:00:15,5s
Work on project,22-09-27 10:00:30,22-09-27 10:08:21,7m 51s
Segment 4,22-09-28 01:27:24,22-09-28 01:27:29,5s
Segment 5,22-09-28 01:28:15,22-09-28 01:28:18,3s
``` ```

View file

@ -0,0 +1,6 @@
{
"nodes":[
{"type":"file","file":"test/Cool Project.md","id":"e41a2deb229880a8","x":-720,"y":-620,"width":1120,"height":1000}
],
"edges":[]
}

View file

@ -6,5 +6,6 @@
"0.1.3": "0.15.0", "0.1.3": "0.15.0",
"0.1.4": "0.15.0", "0.1.4": "0.15.0",
"0.1.5": "0.15.0", "0.1.5": "0.15.0",
"0.1.6": "0.15.0" "0.1.6": "0.15.0",
"0.1.7": "1.2.8"
} }