Compare commits

...

41 commits

Author SHA1 Message Date
Ell 3186f1ba4d Merge remote-tracking branch 'origin/master' 2024-05-08 17:25:50 +02:00
Ell ea04e198d9 add discord to readme 2024-05-08 17:25:42 +02:00
Jacobtread bf9ac5636c
Feature prompt before delete (#48)
* fix: file rename bug

* feat: prompt user before deleting
2024-05-01 10:40:26 +02:00
Ell 6823de48a0 get rid of the roadmap section in the README 2024-04-15 11:31:26 +02:00
Jacobtread 70cb1594ef
fix: file rename bug (#46) 2024-04-15 11:24:15 +02:00
Ell 66eb45d253 0.2.1 2024-03-27 14:26:24 +01:00
Ell 4b6abebe61 still allow editing name and start time while running
closes #41
2024-03-27 14:24:57 +01:00
Ell 4c88c3282d finished up segment name markdown rendering 2024-03-27 14:21:45 +01:00
artel-ksikora 739e51326f
feat: add tag display to Segment (#43)
* feat: add tag display to Segment

* feat: Fragment markdown rework

* feat: Fragment markdown rework

* fixL prettifer remove

* fix: remove prettifer ignore
2024-03-18 16:11:11 +01:00
Ell 48d576aaa2 0.2.0 2024-02-26 15:37:54 +01:00
Ell 701be28601 use the hover background color 2024-02-26 15:36:28 +01:00
Ell 1d90a17e69 added timestamp display for durations
closes #20
2024-02-26 15:21:24 +01:00
Ell 7633ac8144 hover highlighting for the table 2024-02-26 14:56:31 +01:00
Ell 7e108a85cb right-align the buttons 2024-02-26 14:53:33 +01:00
Ell 5bd9fefe60 only indent the names, not the values 2024-02-26 14:41:25 +01:00
Ell 3469232d3b fixed some issues with how and when entry content is displayed
closes #36
2024-02-26 14:39:48 +01:00
Ell 92b2a287aa allow displaying segments in reverse
closes #39
2024-02-26 14:24:21 +01:00
Ell 51d1183c0f 0.1.8 2023-09-08 14:06:12 +02:00
Ell 17471e2831 fixed (newly introduced) exception when trying to edit a running timer 2023-09-08 14:02:15 +02:00
Ell e95f62fb9d store timestamps as iso instead of unix
closes #26
2023-09-08 13:59:50 +02:00
Ell f766fa7210 merge 2023-09-08 13:26:35 +02:00
Ell 3be8d8de02 ignore vault files 2023-08-11 19:32:43 +02:00
Matt Wiseley a6727bb879 Handle active timer with no end value 2023-07-11 22:17:42 -04:00
Matt Wiseley 3d9e6163e7 Use fixed format for editable timestamps 2023-07-05 09:57:58 -04:00
Matt Wiseley 0612a4bd21 24 hour default time stamp format 2023-07-04 17:51:54 -04:00
Matt Wiseley 5677e02243 Remove unneeded width setting 2023-07-03 12:58:04 -04:00
Matt Wiseley 94631bc293 Editable timestamps and mobile horizontal scroll 2023-07-03 12:23:19 -04:00
Ell 941c5e261b 0.1.7 2023-05-23 18:21:10 +02:00
Ell b5e768d99c added a setting to make duration displays less fine-grained
closes #15
2023-05-23 18:18:28 +02:00
Ell 175ef1e0d7 fixed the time tracker not saving correctly on a canvas
closes #16
2023-05-23 18:06:29 +02:00
Pedro J 5baf96571e
Fixed Copy to Table feature to start and end with | character in each row and be compatible with other plugins like Advanced Tables (#13) 2023-05-23 16:48:18 +02:00
Ell 7aa91901c6 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	manifest.json
#	package.json
#	versions.json
2023-05-22 20:02:36 +02:00
Ell 70ade8ada0 cleanup 2023-05-22 20:01:50 +02:00
Ell 05a4710b8a 0.1.6 2022-12-03 22:11:57 +01:00
Ell e7815334b2
Merge pull request #12 from commonCereal/more-than-hour-durations
Update Duration Accumulation to support displaying durations longer than 24 hours
Closes #11
2022-11-23 18:07:22 +01:00
Kyle Ferguson 967d331172 Ran npm install. Removed space in test md file. 2022-11-23 10:05:51 -07:00
Kyle Ferguson 85ee96f5dd Updated formatting of duration to allow for more than 1 24 hour day durations. 2022-11-23 09:53:04 -07:00
Ell 402b97c799 0.1.5 2022-10-19 16:40:01 +02:00
Ell 1bda2accf4 added the ability to create sub-entries 2022-10-19 16:35:21 +02:00
Ell 132e2088be 0.1.4 2022-10-10 15:06:52 +02:00
Ell bce221aaaa fixed edited entry names not actually being saved 2022-10-10 15:05:59 +02:00
26 changed files with 2454 additions and 1978 deletions

View file

@ -1,9 +1,9 @@
# top-most EditorConfig file
root = true
[*]
charset = utf-8
insert_final_newline = true
indent_style = tab
indent_size = 4
tab_width = 4
# top-most EditorConfig file
root = true
[*]
charset = utf-8
insert_final_newline = true
indent_style = space
indent_size = 4
tab_width = 4

49
.gitignore vendored
View file

@ -1,23 +1,26 @@
# vscode
.vscode
# Intellij
*.iml
.idea
# npm
node_modules
# Don't include the compiled main.js file in the repo.
# They should be uploaded to GitHub releases instead.
/main.js
# Exclude sourcemaps
*.map
# obsidian
workspace
workspace.json
# Exclude macOS Finder (System Explorer) View States
.DS_Store
# vscode
.vscode
# Intellij
*.iml
.idea
# npm
node_modules
# Don't include the compiled main.js file in the repo.
# They should be uploaded to GitHub releases instead.
/main.js
# Exclude local settings
data.json
# Exclude sourcemaps
*.map
# obsidian
workspace
workspace.json
# Exclude macOS Finder (System Explorer) View States
.DS_Store

View file

@ -8,16 +8,16 @@ To get started tracking your time with Super Simple Time Tracker, open up the no
When switching to live preview or reading mode, you will now see the time tracker you just inserted! Now, simply name the first segment (or leave the box empty if you don't want to name it) and press the **Start** button. Once you're done with the thing you were doing, simply press the **End** button and the time you spent will be saved and displayed to you in the table.
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)
# 👀 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.
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.
The tracker's information is stored in the code block as JSON data. The names, start times and end times of each segment are stored. They're displayed neatly in the code block in preview or reading mode.
# 🛣️ Roadmap
Super Simple Time Tracker is still in its early stages! There are a lot of plans for it, including:
- A setting to link segments to corresponding daily notes automatically
# 🙏 Acknowledgements
If you like this plugin and want to support its development, you can do so through my website by clicking this fancy image!
[![Support me (if you want), via Patreon, Ko-fi or GitHub Sponsors](https://ellpeck.de/res/generalsupport.png)](https://ellpeck.de/support)
[![Support me (if you want), via Patreon, Ko-fi or GitHub Sponsors](https://ellpeck.de/res/generalsupport-wide.png)](https://ellpeck.de/support)

View file

@ -11,50 +11,50 @@ if you want to view the source, please visit the github repository of this plugi
const prod = (process.argv[2] === 'production');
esbuild.build({
banner: {
js: banner,
},
entryPoints: ['src/main.ts'],
bundle: true,
external: [
'obsidian',
'electron',
'@codemirror/autocomplete',
'@codemirror/closebrackets',
'@codemirror/collab',
'@codemirror/commands',
'@codemirror/comment',
'@codemirror/fold',
'@codemirror/gutter',
'@codemirror/highlight',
'@codemirror/history',
'@codemirror/language',
'@codemirror/lint',
'@codemirror/matchbrackets',
'@codemirror/panel',
'@codemirror/rangeset',
'@codemirror/rectangular-selection',
'@codemirror/search',
'@codemirror/state',
'@codemirror/stream-parser',
'@codemirror/text',
'@codemirror/tooltip',
'@codemirror/view',
...builtins
],
plugins: [
copy({
assets: [{
from: ["./manifest.json", "./main.js", "./styles.css"],
to: ["./test-vault/.obsidian/plugins/simple-time-tracker/."]
}]
}),
],
format: 'cjs',
watch: !prod,
target: 'es2016',
logLevel: "info",
sourcemap: prod ? false : 'inline',
treeShaking: true,
outfile: 'main.js',
banner: {
js: banner,
},
entryPoints: ['src/main.ts'],
bundle: true,
external: [
'obsidian',
'electron',
'@codemirror/autocomplete',
'@codemirror/closebrackets',
'@codemirror/collab',
'@codemirror/commands',
'@codemirror/comment',
'@codemirror/fold',
'@codemirror/gutter',
'@codemirror/highlight',
'@codemirror/history',
'@codemirror/language',
'@codemirror/lint',
'@codemirror/matchbrackets',
'@codemirror/panel',
'@codemirror/rangeset',
'@codemirror/rectangular-selection',
'@codemirror/search',
'@codemirror/state',
'@codemirror/stream-parser',
'@codemirror/text',
'@codemirror/tooltip',
'@codemirror/view',
...builtins
],
plugins: [
copy({
assets: [{
from: ["./manifest.json", "./main.js", "./styles.css"],
to: ["./test-vault/.obsidian/plugins/simple-time-tracker/."]
}]
}),
],
format: 'cjs',
watch: !prod,
target: 'es2016',
logLevel: "info",
sourcemap: prod ? false : 'inline',
treeShaking: true,
outfile: 'main.js',
}).catch(() => process.exit(1));

View file

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

3027
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,24 @@
{
"name": "simple-time-tracker",
"version": "0.1.3",
"description": "Multi-purpose time trackers for your notes!",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
"author": "Ellpeck",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",
"builtin-modules": "^3.2.0",
"electron": "^13.6.2",
"esbuild": "0.14.0",
"esbuild-plugin-copy": "^1.3.0",
"obsidian": "latest",
"tslib": "2.3.1",
"typescript": "4.4.4"
}
"name": "simple-time-tracker",
"version": "0.2.1",
"description": "Multi-purpose time trackers for your notes!",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
"author": "Ellpeck",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",
"builtin-modules": "^3.2.0",
"electron": "^13.6.2",
"esbuild": "0.14.0",
"esbuild-plugin-copy": "^1.3.0",
"obsidian": "latest",
"tslib": "2.3.1",
"typescript": "4.4.4"
}
}

View file

@ -1,37 +1,56 @@
import { Plugin } from "obsidian";
import {MarkdownRenderChild, Plugin, TFile} from "obsidian";
import { defaultSettings, SimpleTimeTrackerSettings } from "./settings";
import { SimpleTimeTrackerSettingsTab } from "./settings-tab";
import { displayTracker, loadTracker } from "./tracker";
export default class SimpleTimeTrackerPlugin extends Plugin {
settings: SimpleTimeTrackerSettings;
settings: SimpleTimeTrackerSettings;
async onload(): Promise<void> {
await this.loadSettings();
async onload(): Promise<void> {
await this.loadSettings();
this.addSettingTab(new SimpleTimeTrackerSettingsTab(this.app, this));
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.getSectionInfo(e), this.settings);
});
this.registerMarkdownCodeBlockProcessor("simple-time-tracker", (s, e, i) => {
e.empty();
let component = new MarkdownRenderChild(e)
let tracker = loadTracker(s);
this.addCommand({
id: `insert`,
name: `Insert Time Tracker`,
editorCallback: (e, _) => {
e.replaceSelection("```simple-time-tracker\n```\n");
}
});
}
// Initial file name
let filePath = i.sourcePath;
async loadSettings() {
this.settings = Object.assign({}, defaultSettings, await this.loadData());
}
// Getter passed to displayTracker since the file name can change
const getFile = () => filePath;
async saveSettings() {
await this.saveData(this.settings);
}
// Hook rename events to update the file path
const renameEventRef = this.app.vault.on("rename", (file, oldPath) => {
if (file instanceof TFile && oldPath === filePath) {
filePath = file.path;
}
})
// Register the event to remove on unload
component.registerEvent(renameEventRef);
displayTracker(tracker, e, getFile, () => i.getSectionInfo(e), this.settings, component);
i.addChild(component)
});
this.addCommand({
id: `insert`,
name: `Insert Time Tracker`,
editorCallback: (e, _) => {
e.replaceSelection("```simple-time-tracker\n```\n");
}
});
}
async loadSettings(): Promise<void> {
this.settings = Object.assign({}, defaultSettings, await this.loadData());
}
async saveSettings(): Promise<void> {
await this.saveData(this.settings);
}
}

View file

@ -1,6 +1,6 @@
import { App, PluginSettingTab, Setting } from "obsidian";
import {App, PluginSettingTab, Setting} from "obsidian";
import SimpleTimeTrackerPlugin from "./main";
import { defaultSettings } from "./settings";
import {defaultSettings} from "./settings";
export class SimpleTimeTrackerSettingsTab extends PluginSettingTab {
@ -13,14 +13,14 @@ export class SimpleTimeTrackerSettingsTab extends PluginSettingTab {
display(): void {
this.containerEl.empty();
this.containerEl.createEl("h2", { text: "Super Simple Time Tracker Settings" });
this.containerEl.createEl("h2", {text: "Super Simple Time Tracker Settings"});
new 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." });
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));
@ -41,9 +41,45 @@ 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();
});
});
new Setting(this.containerEl)
.setName("Timestamp Durations")
.setDesc("Whether durations should be displayed in a timestamp format (12:15:01) rather than the default duration format (12h 15m 1s).")
.addToggle(t => {
t.setValue(this.plugin.settings.timestampDurations);
t.onChange(async v => {
this.plugin.settings.timestampDurations = v;
await this.plugin.saveSettings();
});
});
new Setting(this.containerEl)
.setName("Display Segments in Reverse Order")
.setDesc("Whether older tracker segments should be displayed towards the bottom of the tracker, rather than the top.")
.addToggle(t => {
t.setValue(this.plugin.settings.reverseSegmentOrder);
t.onChange(async v => {
this.plugin.settings.reverseSegmentOrder = v;
await 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" });
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"
});
}
}

View file

@ -1,11 +1,19 @@
export const defaultSettings: SimpleTimeTrackerSettings = {
timestampFormat: "YY-MM-DD hh:mm:ss",
csvDelimiter: ","
timestampFormat: "YY-MM-DD HH:mm:ss",
editableTimestampFormat: "YYYY-MM-DD HH:mm:ss",
csvDelimiter: ",",
fineGrainedDurations: true,
reverseSegmentOrder: false,
timestampDurations: false
};
export interface SimpleTimeTrackerSettings {
timestampFormat: string;
editableTimestampFormat: string;
csvDelimiter: string;
fineGrainedDurations: boolean;
reverseSegmentOrder: boolean;
timestampDurations: boolean;
}

View file

@ -1,5 +1,5 @@
import { moment, App, MarkdownSectionInformation, ButtonComponent, TextComponent } from "obsidian";
import { SimpleTimeTrackerSettings } from "./settings";
import {moment, App, MarkdownSectionInformation, ButtonComponent, TextComponent, TFile, MarkdownRenderer, Component, MarkdownRenderChild} from "obsidian";
import {SimpleTimeTrackerSettings} from "./settings";
export interface Tracker {
entries: Entry[];
@ -7,29 +7,13 @@ export interface Tracker {
export interface Entry {
name: string;
startTime: number;
endTime: number;
startTime: string;
endTime: string;
subEntries: Entry[];
}
export function startEntry(tracker: Tracker, name: string): void {
if (!name)
name = `Segment ${tracker.entries.length + 1}`;
let entry: Entry = { name: name, startTime: moment().unix(), endTime: null };
tracker.entries.push(entry);
};
export function endEntry(tracker: Tracker): void {
let last = tracker.entries.last();
last.endTime = moment().unix();
}
export function isRunning(tracker: Tracker): boolean {
let last = tracker.entries.last();
return last != null && !last.endTime;
}
export async function saveTracker(tracker: Tracker, app: App, section: MarkdownSectionInformation): Promise<void> {
let file = app.workspace.getActiveFile();
export async function saveTracker(tracker: Tracker, app: App, fileName: string, section: MarkdownSectionInformation): Promise<void> {
let file = app.vault.getAbstractFileByPath(fileName) as TFile;
if (!file)
return;
let content = await app.vault.read(file);
@ -47,15 +31,21 @@ export async function saveTracker(tracker: Tracker, app: App, section: MarkdownS
export function loadTracker(json: string): Tracker {
if (json) {
try {
return JSON.parse(json);
let ret = JSON.parse(json);
fixLegacyTimestamps(ret.entries);
return ret;
} catch (e) {
console.log(`Failed to parse Tracker from ${json}`);
}
}
return { entries: [] };
return {entries: []};
}
export function displayTracker(tracker: Tracker, element: HTMLElement, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings): void {
type GetFile = () => string;
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
let running = isRunning(tracker);
let btn = new ButtonComponent(element)
@ -64,80 +54,42 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, getSectio
.setTooltip(running ? "End" : "Start")
.onClick(async () => {
if (running) {
endEntry(tracker);
endRunningEntry(tracker);
} else {
startEntry(tracker, name.getValue());
startNewEntry(tracker, newSegmentNameBox.getValue());
}
await saveTracker(tracker, this.app, getSectionInfo());
await saveTracker(tracker, this.app, getFile(), getSectionInfo());
});
btn.buttonEl.addClass("simple-time-tracker-btn");
let name = new TextComponent(element)
let newSegmentNameBox = new TextComponent(element)
.setPlaceholder("Segment name")
.setDisabled(running);
name.inputEl.addClass("simple-time-tracker-txt");
newSegmentNameBox.inputEl.addClass("simple-time-tracker-txt");
// add timers
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" });
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) {
// add table
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", {text: "Segment"}),
createEl("th", {text: "Start time"}),
createEl("th", {text: "End time"}),
createEl("th", {text: "Duration"}),
createEl("th"));
for (let entry of tracker.entries) {
let row = table.createEl("tr");
let name = row.createEl("td");
let namePar = name.createEl("span", { text: entry.name });
let nameBox = new TextComponent(name).setValue(entry.name);
nameBox.inputEl.hidden = true;
row.createEl("td", { text: formatTimestamp(entry.startTime, settings) });
row.createEl("td", { text: entry.endTime ? formatTimestamp(entry.endTime, settings) : "" });
row.createEl("td", { text: entry.endTime ? formatDurationBetween(entry.startTime, entry.endTime) : "" });
let entryButtons = row.createEl("td");
let editButton = new ButtonComponent(entryButtons)
.setClass("clickable-icon")
.setTooltip("Edit")
.setIcon("lucide-pencil")
.onClick(() => {
if (namePar.hidden) {
namePar.hidden = false;
nameBox.inputEl.hidden = true;
if (nameBox.getValue())
namePar.setText(nameBox.getValue());
editButton.setIcon("lucide-pencil");
} else {
namePar.hidden = true;
nameBox.inputEl.hidden = false;
nameBox.setValue(namePar.getText());
editButton.setIcon("lucide-check");
}
});
new ButtonComponent(entryButtons)
.setClass("clickable-icon")
.setTooltip("Remove")
.setIcon("lucide-trash")
.onClick(async () => {
tracker.entries.remove(entry);
await saveTracker(tracker, this.app, getSectionInfo());
});
}
for (let entry of orderedEntries(tracker.entries, settings))
addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, getFile, getSectionInfo, settings, 0, component);
// add copy buttons
let buttons = element.createEl("div", { cls: "simple-time-tracker-bottom" });
let buttons = element.createEl("div", {cls: "simple-time-tracker-bottom"});
new ButtonComponent(buttons)
.setButtonText("Copy as table")
.onClick(() => navigator.clipboard.writeText(createMarkdownTable(tracker, settings)));
@ -147,60 +99,170 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, getSectio
}
setCountdownValues(tracker, current, total, currentDiv);
setCountdownValues(tracker, current, total, currentDiv, settings);
let intervalId = window.setInterval(() => {
// we delete the interval timer when the element is removed
if (!element.isConnected) {
window.clearInterval(intervalId);
return;
}
setCountdownValues(tracker, current, total, currentDiv);
setCountdownValues(tracker, current, total, currentDiv, settings);
}, 1000);
}
function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLElement, currentDiv: HTMLDivElement) {
let currEntry = tracker.entries.last();
if (currEntry) {
if (!currEntry.endTime)
current.setText(formatDurationBetween(currEntry.startTime, moment().unix()));
total.setText(formatDuration(getTotalDuration(tracker)));
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) {
entry.subEntries = [{...entry, name: `Part 1`}];
entry.startTime = null;
entry.endTime = null;
}
currentDiv.hidden = !currEntry || !!currEntry.endTime;
if (!name)
name = `Part ${entry.subEntries.length + 1}`;
entry.subEntries.push({name: name, startTime: moment().toISOString(), endTime: null, subEntries: null});
}
function getTotalDuration(tracker: Tracker): number {
let totalDuration = 0;
for (let entry of tracker.entries) {
let endTime = entry.endTime ? moment.unix(entry.endTime) : moment();
totalDuration += endTime.diff(moment.unix(entry.startTime));
function startNewEntry(tracker: Tracker, name: string): void {
if (!name)
name = `Segment ${tracker.entries.length + 1}`;
let entry: Entry = {name: name, startTime: moment().toISOString(), endTime: null, subEntries: null};
tracker.entries.push(entry);
}
function endRunningEntry(tracker: Tracker): void {
let entry = getRunningEntry(tracker.entries);
entry.endTime = moment().toISOString();
}
function removeEntry(entries: Entry[], toRemove: Entry): boolean {
if (entries.contains(toRemove)) {
entries.remove(toRemove);
return true;
} else {
for (let entry of entries) {
if (entry.subEntries && removeEntry(entry.subEntries, toRemove)) {
// if we only have one sub entry remaining, we can merge back into our main entry
if (entry.subEntries.length == 1) {
let single = entry.subEntries[0];
entry.startTime = single.startTime;
entry.endTime = single.endTime;
entry.subEntries = null;
}
return true;
}
}
}
return totalDuration;
return false;
}
function formatTimestamp(timestamp: number, settings: SimpleTimeTrackerSettings): string {
return moment.unix(timestamp).format(settings.timestampFormat);
function isRunning(tracker: Tracker): boolean {
return !!getRunningEntry(tracker.entries);
}
function formatDurationBetween(startTime: number, endTime: number): string {
return formatDuration(moment.unix(endTime).diff(moment.unix(startTime)));
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 formatDuration(totalTime: number): string {
let duration = moment.duration(totalTime);
let ret = "";
if (duration.hours() > 0)
ret += duration.hours() + "h ";
if (duration.minutes() > 0)
ret += duration.minutes() + "m ";
ret += duration.seconds() + "s";
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) {
current.setText(formatDuration(getDuration(running), settings));
currentDiv.hidden = false;
} else {
currentDiv.hidden = true;
}
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);
}
function unformatEditableTimestamp(formatted: string, settings: SimpleTimeTrackerSettings): string {
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))
entry.startTime = moment.unix(+entry.startTime).toISOString();
if (entry.endTime && !isNaN(+entry.endTime))
entry.endTime = moment.unix(+entry.endTime).toISOString();
if (entry.subEntries)
fixLegacyTimestamps(entry.subEntries);
}
}
function createMarkdownTable(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
let table = [["Segment", "Start time", "End time", "Duration"]];
for (let entry of tracker.entries)
table.push(createTableRow(entry, settings));
table.push(["**Total**", "", "", `**${formatDuration(getTotalDuration(tracker))}**`]);
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
@ -208,27 +270,182 @@ function createMarkdownTable(tracker: Tracker, settings: SimpleTimeTrackerSettin
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";
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";
ret += "| " + row.join(" | ") + " |\n";
}
return ret;
}
function createCsv(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
let ret = "";
for (let entry of tracker.entries)
ret += createTableRow(entry, settings).join(settings.csvDelimiter) + "\n";
for (let entry of orderedEntries(tracker.entries, settings)) {
for (let row of createTableSection(entry, settings))
ret += row.join(settings.csvDelimiter) + "\n";
}
return ret;
}
function createTableRow(entry: Entry, settings: SimpleTimeTrackerSettings): string[] {
return [
function createTableSection(entry: Entry, settings: SimpleTimeTrackerSettings): string[][] {
let ret = [[
entry.name,
formatTimestamp(entry.startTime, settings),
entry.startTime ? formatTimestamp(entry.startTime, settings) : "",
entry.endTime ? formatTimestamp(entry.endTime, settings) : "",
entry.endTime ? formatDurationBetween(entry.startTime, entry.endTime) : ""];
entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : ""]];
if (entry.subEntries) {
for (let sub of orderedEntries(entry.subEntries, settings))
ret.push(...createTableSection(sub, settings));
}
return ret;
}
function orderedEntries(entries: Entry[], settings: SimpleTimeTrackerSettings): Entry[] {
return settings.reverseSegmentOrder ? entries.slice().reverse() : entries;
}
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");
let nameField = new EditableField(row, indent, entry.name);
let startField = new EditableTimestampField(row, (entry.startTime), settings);
let endField = new EditableTimestampField(row, (entry.endTime), settings);
row.createEl("td", {text: entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : ""});
renderNameAsMarkdown(nameField.label, getFile, component);
let entryButtons = row.createEl("td");
entryButtons.addClass("simple-time-tracker-table-buttons");
new ButtonComponent(entryButtons)
.setClass("clickable-icon")
.setIcon(`lucide-play`)
.setTooltip("Continue")
.setDisabled(trackerRunning)
.onClick(async () => {
startSubEntry(entry, newSegmentNameBox.getValue());
await saveTracker(tracker, this.app, getFile(), getSectionInfo());
});
let editButton = new ButtonComponent(entryButtons)
.setClass("clickable-icon")
.setTooltip("Edit")
.setIcon("lucide-pencil")
.onClick(async () => {
if (nameField.editing()) {
entry.name = nameField.endEdit();
startField.endEdit();
entry.startTime = startField.getTimestamp();
if (!entryRunning) {
endField.endEdit();
entry.endTime = endField.getTimestamp();
}
await saveTracker(tracker, this.app, getFile(), getSectionInfo());
editButton.setIcon("lucide-pencil");
renderNameAsMarkdown(nameField.label, getFile, component);
} else {
nameField.beginEdit(entry.name);
// only allow editing start and end times if we don't have sub entries
if (!entry.subEntries) {
startField.beginEdit(entry.startTime);
if (!entryRunning)
endField.beginEdit(entry.endTime);
}
editButton.setIcon("lucide-check");
}
});
new ButtonComponent(entryButtons)
.setClass("clickable-icon")
.setTooltip("Remove")
.setIcon("lucide-trash")
.setDisabled(entryRunning)
.onClick(async () => {
if (!confirm("Are you sure you want to delete this entry?")) {
return;
}
removeEntry(tracker.entries, entry);
await saveTracker(tracker, this.app, getFile(), getSectionInfo());
});
if (entry.subEntries) {
for (let sub of orderedEntries(entry.subEntries, settings))
addEditableTableRow(tracker, sub, table, newSegmentNameBox, trackerRunning, getFile, getSectionInfo, settings, indent + 1, component);
}
}
function renderNameAsMarkdown(label: HTMLSpanElement, getFile: GetFile, component: Component): void {
void MarkdownRenderer.renderMarkdown(label.innerHTML, label, getFile(), component);
// rendering wraps it in a paragraph
label.innerHTML = label.querySelector("p").innerHTML;
}
class EditableField {
cell: HTMLTableCellElement;
label: HTMLSpanElement;
box: TextComponent;
constructor(row: HTMLTableRowElement, indent: number, value: string) {
this.cell = row.createEl("td");
this.label = this.cell.createEl("span", {text: value});
this.label.style.marginLeft = `${indent}em`;
this.box = new TextComponent(this.cell).setValue(value);
this.box.inputEl.addClass("simple-time-tracker-input");
this.box.inputEl.hide();
}
editing(): boolean {
return this.label.hidden;
}
beginEdit(value: string): void {
this.label.hidden = true;
this.box.setValue(value);
this.box.inputEl.show();
}
endEdit(): string {
const value = this.box.getValue();
this.label.setText(value);
this.box.inputEl.hide();
this.label.hidden = false;
return value;
}
}
class EditableTimestampField extends EditableField {
settings: SimpleTimeTrackerSettings;
constructor(row: HTMLTableRowElement, value: string, settings: SimpleTimeTrackerSettings) {
super(row, 0, value ? formatTimestamp(value, settings) : "");
this.settings = settings;
}
beginEdit(value: string): void {
super.beginEdit(value ? formatEditableTimestamp(value, this.settings) : "");
}
endEdit(): string {
const value = this.box.getValue();
let displayValue = value;
if (value) {
const timestamp = unformatEditableTimestamp(value, this.settings);
displayValue = formatTimestamp(timestamp, this.settings);
}
this.label.setText(displayValue);
this.box.inputEl.hide();
this.label.hidden = false;
return value;
}
getTimestamp(): string {
if (this.box.getValue()) {
return unformatEditableTimestamp(this.box.getValue(), this.settings);
} else {
return null;
}
}
}

View file

@ -1,64 +1,82 @@
.simple-time-tracker-container {
overflow-x: scroll;
}
.simple-time-tracker-support {
max-width: 50%;
width: 400px;
height: auto;
max-width: 50%;
width: 400px;
height: auto;
}
.simple-time-tracker-btn,
.simple-time-tracker-txt {
display: block;
margin-left: auto;
margin-right: auto;
display: block;
margin-left: auto;
margin-right: auto;
}
.simple-time-tracker-txt {
text-align: center;
text-align: center;
}
.simple-time-tracker-btn {
margin-top: 10px;
margin-bottom: 10px;
margin-top: 10px;
margin-bottom: 10px;
}
.simple-time-tracker-btn svg {
width: 32px;
height: 32px;
width: 32px;
height: 32px;
}
.simple-time-tracker-bottom button {
margin: 10px 5px 10px 5px;
margin: 10px 5px 10px 5px;
}
.simple-time-tracker-timers,
.simple-time-tracker-bottom {
display: flex;
justify-content: center;
text-align: center;
display: flex;
justify-content: center;
text-align: center;
}
.simple-time-tracker-timers span {
display: block;
display: block;
}
.simple-time-tracker-timer {
margin: 20px;
margin: 20px;
}
.simple-time-tracker-timer-time {
font-size: xx-large;
font-weight: bolder;
font-size: xx-large;
font-weight: bolder;
}
.simple-time-tracker-table {
width: 100%;
margin-top: 20px;
width: 100%;
margin-top: 20px;
}
.simple-time-tracker-table td,
.simple-time-tracker-table th {
border: none;
vertical-align: middle;
border: none;
}
.simple-time-tracker-table .clickable-icon {
display: inline;
display: inline;
}
.simple-time-tracker-input {
max-width: 150px;
min-width: 100px;
}
.simple-time-tracker-table-buttons {
text-align: right !important;
}
.simple-time-tracker-table tr:hover {
background-color: var(--background-modifier-hover);
}

View file

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

View file

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

View file

@ -0,0 +1,3 @@
*
!.gitignore
!data.json

View file

@ -1,4 +1,8 @@
{
"timestampFormat": "YY-MM-DD hh:mm:ss",
"csvDelimiter": ","
}
"editableTimestampFormat": "YYYY-MM-DD HH:mm:ss",
"csvDelimiter": ",",
"fineGrainedDurations": true,
"reverseSegmentOrder": false,
"timestampDurations": true
}

File diff suppressed because one or more lines are too long

View file

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

View file

@ -1,64 +0,0 @@
.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

@ -1,18 +0,0 @@
# Notes
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
{"entries":[{"name":"Think about project","startTime":1664305777,"endTime":1664308788},{"name":"Create project note","startTime":1664308810,"endTime":1664308815},{"name":"Work on project","startTime":1664308830,"endTime":1664309301},{"name":"Segment 4","startTime":1664364444,"endTime":1664364449},{"name":"Segment 5","startTime":1664364495,"endTime":1664364498},{"name":"Segment 6","startTime":1664458520,"endTime":1664458523},{"name":"Segment 7","startTime":1664460326,"endTime":1664460329}]}
```
```
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,10 @@
# Notes
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
{"entries":[{"name":"test","startTime":"2020-08-01T07:00:00.000Z","endTime":"2021-08-02T07:00:00.000Z","subEntries":null},{"name":"test","startTime":"2021-08-01T07:00:00.000Z","endTime":"2021-10-02T07:00:00.000Z","subEntries":null},{"name":"test","startTime":"1970-01-01T00:00:00.000Z","endTime":null,"subEntries":[{"name":"Part 1","startTime":"2022-08-01T07:00:00.000Z","endTime":"2022-08-02T07:00:00.000Z","subEntries":null},{"name":"Part 2","startTime":"2024-02-26T13:19:40.629Z","endTime":"2024-02-26T13:19:43.713Z","subEntries":null},{"name":"Part 3","startTime":"1970-01-01T00:00:00.000Z","endTime":null,"subEntries":[{"name":"Part 1","startTime":"2024-02-26T13:23:51.939Z","endTime":"2024-02-26T13:23:54.232Z","subEntries":null},{"name":"Part 2","startTime":"2024-02-26T13:27:34.397Z","endTime":"2024-02-26T13:27:49.282Z","subEntries":null}]},{"name":"Part 4","startTime":"2024-02-26T13:29:06.983Z","endTime":"2024-02-26T13:29:20.770Z","subEntries":null}]},{"name":"test","startTime":"2022-10-01T12:30:10.000Z","endTime":"2022-10-01T13:40:05.000Z","subEntries":null},{"name":"Segment 5","startTime":"1970-01-01T00:00:00.000Z","endTime":null,"subEntries":[{"name":"Part 1","startTime":"2023-05-23T16:16:56.000Z","endTime":"2023-05-23T16:16:59.000Z","subEntries":null},{"name":"Part 2","startTime":"1970-01-01T00:00:00.000Z","endTime":null,"subEntries":[{"name":"Part 1","startTime":"2024-02-26T13:30:39.632Z","endTime":"2024-02-26T13:30:56.290Z","subEntries":null},{"name":"Part 2","startTime":"2024-02-26T13:30:57.000Z","endTime":"2024-02-26T13:31:00.000Z","subEntries":null}]},{"name":"Part 3","startTime":"2024-02-26T13:34:18.537Z","endTime":"2024-02-26T13:34:21.169Z","subEntries":null}]}]}
```
```simple-time-tracker
{"entries":[{"name":"Segment 1","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":"2024-02-26T13:37:59.292Z","endTime":"2024-02-26T13:38:01.437Z","subEntries":null},{"name":"Part 2","startTime":"2024-02-26T14:04:14.156Z","endTime":"2024-02-26T14:04:30.576Z","subEntries":null}]},{"name":"Part 2","startTime":"2024-02-26T13:38:16.235Z","endTime":"2024-02-26T13:38:18.895Z","subEntries":null}]}]}
```

View file

@ -0,0 +1,4 @@
Tested for #tag, *italic*, [link](test2), etc:
```simple-time-tracker
{"entries":[{"name":"`Segment 1`","startTime":"2022-09-27T19:51:18.000Z","endTime":"2022-09-27T19:51:24.000Z"},{"name":"Segment 2","startTime":"2022-09-27T19:51:25.000Z","endTime":"2022-09-27T19:51:26.000Z"},{"name":"#tag Seqment 3 *add* #tag1 text","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1 #tagp1","startTime":"2024-03-17T11:16:00.382Z","endTime":"2024-03-17T11:16:15.966Z","subEntries":null},{"name":"Part 3","startTime":"2024-03-17T11:17:08.000Z","endTime":"2024-03-17T11:17:24.000Z","subEntries":null}]},{"name":"#tag3 Segment 4","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1 #tag4","startTime":"2024-03-17T12:22:04.000Z","endTime":"2024-03-17T12:22:16.000Z","subEntries":null},{"name":"#tag5 Part 2 *italic*","startTime":"2024-03-17T12:22:20.000Z","endTime":"2024-03-17T12:22:24.000Z","subEntries":null}]},{"name":"*italic* Segment 5 #tag6 [test2](test2)","startTime":"2024-03-17T12:40:37.000Z","endTime":"2024-03-17T12:40:45.000Z","subEntries":null},{"name":"Segment 6","startTime":"2024-03-27T13:20:56.000Z","endTime":null,"subEntries":null}]}
```

View file

@ -0,0 +1,6 @@
# Notes
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
{"entries":[{"name":"Segment 1","startTime":"2022-10-19T14:32:28.000Z","endTime":"2022-10-19T14:32:31.000Z","subEntries":null},{"name":"Segment 2","startTime":"2022-10-19T14:32:33.000Z","endTime":"2022-10-19T14:32:41.000Z","subEntries":null},{"name":"Segment 3","startTime":"1970-01-01T00:00:00.000Z","endTime":null,"subEntries":[{"name":"Part 1","startTime":"2022-10-19T14:32:42.000Z","endTime":"2022-10-19T14:33:15.000Z","subEntries":null},{"name":"Part 2","startTime":"2022-10-19T14:33:24.000Z","endTime":"2022-10-19T14:33:45.000Z","subEntries":null},{"name":"Part 3","startTime":"2022-10-19T14:34:54.000Z","endTime":"2022-10-19T14:35:01.000Z","subEntries":null}]},{"name":"Segment 4","startTime":"2022-10-19T14:34:48.000Z","endTime":"2022-10-19T14:34:51.000Z","subEntries":null},{"name":"Segment 5","startTime":"2023-05-23T16:01:44.000Z","endTime":"2023-05-23T16:01:48.000Z","subEntries":null},{"name":"Segment 6","startTime":"2023-05-23T16:01:50.000Z","endTime":"2023-05-23T16:01:52.000Z","subEntries":null},{"name":"Segment 7","startTime":"2023-05-23T16:02:09.000Z","endTime":"2023-05-23T16:02:12.000Z","subEntries":null},{"name":"Segment 8","startTime":"1970-01-01T00:00:00.000Z","endTime":null,"subEntries":[{"name":"Part 1","startTime":"2023-05-23T16:02:20.000Z","endTime":"2023-05-23T16:02:28.000Z","subEntries":null},{"name":"Part 2","startTime":"2023-09-08T11:58:11.000Z","endTime":"2023-09-08T11:58:38.000Z","subEntries":null}]},{"name":"Segment 9","startTime":"2023-09-08T12:00:35.000Z","endTime":"2023-09-08T12:00:49.991Z","subEntries":null},{"name":"Segment 10","startTime":"2023-09-08T12:01:45.000Z","endTime":"2023-09-08T12:01:53.711Z","subEntries":null}]}
```

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

@ -1,23 +1,23 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
]
},
"include": [
"**/*.ts"
]
},
"include": [
"**/*.ts"
]
}

View file

@ -1,7 +1,14 @@
{
"0.0.1": "0.15.0",
"0.1.0": "0.15.0",
"0.1.1": "0.15.0",
"0.1.2": "0.15.0",
"0.1.3": "0.15.0"
"0.0.1": "0.15.0",
"0.1.0": "0.15.0",
"0.1.1": "0.15.0",
"0.1.2": "0.15.0",
"0.1.3": "0.15.0",
"0.1.4": "0.15.0",
"0.1.5": "0.15.0",
"0.1.6": "0.15.0",
"0.1.7": "1.2.8",
"0.1.8": "1.3.0",
"0.2.0": "1.3.0",
"0.2.1": "1.3.0"
}