Compare commits

...

11 commits

Author SHA1 Message Date
Ell 7f75b08e4d 0.2.2 2024-05-19 10:31:50 +02:00
Ell ebecd57b91 added discord link to settings 2024-05-19 10:29:55 +02:00
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
10 changed files with 134 additions and 87 deletions

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.
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

@ -1,7 +1,7 @@
{
"id": "simple-time-tracker",
"name": "Super Simple Time Tracker",
"version": "0.2.0",
"version": "0.2.2",
"minAppVersion": "1.2.8",
"description": "Multi-purpose time trackers for your notes!",
"author": "Ellpeck",

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "simple-time-tracker",
"version": "0.2.0",
"version": "0.2.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "simple-time-tracker",
"version": "0.2.0",
"version": "0.2.2",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",

View file

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

View file

@ -1,4 +1,4 @@
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";
@ -13,9 +13,28 @@ export default class SimpleTimeTrackerPlugin extends Plugin {
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);
let component = new MarkdownRenderChild(e)
let tracker = loadTracker(s);
// Initial file name
let filePath = i.sourcePath;
// Getter passed to displayTracker since the file name can change
const getFile = () => filePath;
// 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({

View file

@ -75,11 +75,15 @@ export class SimpleTimeTrackerSettingsTab extends PluginSettingTab {
});
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: "Need help using the plugin? Feel free to join the Discord server!" });
this.containerEl.createEl("a", { href: "https://link.ellpeck.de/discordweb" }).createEl("img", {
attr: { src: "https://ellpeck.de/res/discord-wide.png" },
cls: "simple-time-tracker-settings-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-wide.png" },
cls: "simple-time-tracker-settings-image"
});
}
}

View file

@ -1,4 +1,4 @@
import {moment, App, MarkdownSectionInformation, ButtonComponent, TextComponent, TFile} from "obsidian";
import {moment, App, MarkdownSectionInformation, ButtonComponent, TextComponent, TFile, MarkdownRenderer, Component, MarkdownRenderChild} from "obsidian";
import {SimpleTimeTrackerSettings} from "./settings";
export interface Tracker {
@ -41,7 +41,10 @@ export function loadTracker(json: string): Tracker {
return {entries: []};
}
export function displayTracker(tracker: Tracker, element: HTMLElement, file: string, 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);
@ -55,7 +58,7 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, file: str
} else {
startNewEntry(tracker, newSegmentNameBox.getValue());
}
await saveTracker(tracker, this.app, file, getSectionInfo());
await saveTracker(tracker, this.app, getFile(), getSectionInfo());
});
btn.buttonEl.addClass("simple-time-tracker-btn");
let newSegmentNameBox = new TextComponent(element)
@ -83,7 +86,7 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, file: str
createEl("th"));
for (let entry of orderedEntries(tracker.entries, settings))
addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, file, getSectionInfo, settings, 0);
addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, getFile, getSectionInfo, settings, 0, component);
// add copy buttons
let buttons = element.createEl("div", {cls: "simple-time-tracker-bottom"});
@ -303,6 +306,83 @@ function orderedEntries(entries: Entry[], settings: SimpleTimeTrackerSettings):
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;
@ -369,64 +449,3 @@ class EditableTimestampField extends EditableField {
}
}
}
function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableElement, newSegmentNameBox: TextComponent, trackerRunning: boolean, file: string, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings, indent: number): 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) : ""});
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, file, getSectionInfo());
});
let editButton = new ButtonComponent(entryButtons)
.setClass("clickable-icon")
.setTooltip("Edit")
.setIcon("lucide-pencil")
.setDisabled(entryRunning)
.onClick(async () => {
if (nameField.editing()) {
entry.name = nameField.endEdit();
startField.endEdit();
entry.startTime = startField.getTimestamp();
endField.endEdit();
entry.endTime = endField.getTimestamp();
await saveTracker(tracker, this.app, file, getSectionInfo());
editButton.setIcon("lucide-pencil");
} 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);
endField.beginEdit(entry.endTime);
}
editButton.setIcon("lucide-check");
}
});
new ButtonComponent(entryButtons)
.setClass("clickable-icon")
.setTooltip("Remove")
.setIcon("lucide-trash")
.setDisabled(entryRunning)
.onClick(async () => {
removeEntry(tracker.entries, entry);
await saveTracker(tracker, this.app, file, getSectionInfo());
});
if (entry.subEntries) {
for (let sub of orderedEntries(entry.subEntries, settings))
addEditableTableRow(tracker, sub, table, newSegmentNameBox, trackerRunning, file, getSectionInfo, settings, indent + 1);
}
}

View file

@ -2,9 +2,8 @@
overflow-x: scroll;
}
.simple-time-tracker-support {
max-width: 50%;
width: 400px;
.simple-time-tracker-settings-image {
width: 100%;
height: auto;
}

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

@ -9,5 +9,7 @@
"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.0": "1.3.0",
"0.2.1": "1.3.0",
"0.2.2": "1.3.0"
}