mirror of
https://github.com/Ellpeck/ObsidianSimpleTimeTracker.git
synced 2024-11-28 03:58:34 +01:00
Compare commits
3 commits
739e51326f
...
66eb45d253
Author | SHA1 | Date | |
---|---|---|---|
66eb45d253 | |||
4b6abebe61 | |||
4c88c3282d |
7 changed files with 89 additions and 95 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "simple-time-tracker",
|
"id": "simple-time-tracker",
|
||||||
"name": "Super Simple Time Tracker",
|
"name": "Super Simple Time Tracker",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"minAppVersion": "1.2.8",
|
"minAppVersion": "1.2.8",
|
||||||
"description": "Multi-purpose time trackers for your notes!",
|
"description": "Multi-purpose time trackers for your notes!",
|
||||||
"author": "Ellpeck",
|
"author": "Ellpeck",
|
||||||
|
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "simple-time-tracker",
|
"name": "simple-time-tracker",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "simple-time-tracker",
|
"name": "simple-time-tracker",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^16.11.6",
|
"@types/node": "^16.11.6",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "simple-time-tracker",
|
"name": "simple-time-tracker",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"description": "Multi-purpose time trackers for your notes!",
|
"description": "Multi-purpose time trackers for your notes!",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Plugin } from "obsidian";
|
import {MarkdownRenderChild, Plugin} from "obsidian";
|
||||||
import { defaultSettings, SimpleTimeTrackerSettings } from "./settings";
|
import { defaultSettings, SimpleTimeTrackerSettings } from "./settings";
|
||||||
import { SimpleTimeTrackerSettingsTab } from "./settings-tab";
|
import { SimpleTimeTrackerSettingsTab } from "./settings-tab";
|
||||||
import { displayTracker, loadTracker } from "./tracker";
|
import { displayTracker, loadTracker } from "./tracker";
|
||||||
|
@ -13,9 +13,11 @@ export default class SimpleTimeTrackerPlugin extends Plugin {
|
||||||
this.addSettingTab(new SimpleTimeTrackerSettingsTab(this.app, this));
|
this.addSettingTab(new SimpleTimeTrackerSettingsTab(this.app, this));
|
||||||
|
|
||||||
this.registerMarkdownCodeBlockProcessor("simple-time-tracker", (s, e, i) => {
|
this.registerMarkdownCodeBlockProcessor("simple-time-tracker", (s, e, i) => {
|
||||||
let tracker = loadTracker(s);
|
|
||||||
e.empty();
|
e.empty();
|
||||||
displayTracker(tracker, e, i.sourcePath, () => i.getSectionInfo(e), this.settings);
|
let component = new MarkdownRenderChild(e)
|
||||||
|
let tracker = loadTracker(s);
|
||||||
|
displayTracker(tracker, e, i.sourcePath, () => i.getSectionInfo(e), this.settings, component);
|
||||||
|
i.addChild(component)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
|
|
163
src/tracker.ts
163
src/tracker.ts
|
@ -1,4 +1,4 @@
|
||||||
import {moment, App, MarkdownSectionInformation, ButtonComponent, TextComponent, TFile, MarkdownRenderer} from "obsidian";
|
import {moment, App, MarkdownSectionInformation, ButtonComponent, TextComponent, TFile, MarkdownRenderer, Component, MarkdownRenderChild} from "obsidian";
|
||||||
import {SimpleTimeTrackerSettings} from "./settings";
|
import {SimpleTimeTrackerSettings} from "./settings";
|
||||||
|
|
||||||
export interface Tracker {
|
export interface Tracker {
|
||||||
|
@ -41,7 +41,7 @@ export function loadTracker(json: string): Tracker {
|
||||||
return {entries: []};
|
return {entries: []};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function displayTracker(tracker: Tracker, element: HTMLElement, file: string, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings): void {
|
export function displayTracker(tracker: Tracker, element: HTMLElement, file: string, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings, component: MarkdownRenderChild): void {
|
||||||
element.addClass("simple-time-tracker-container");
|
element.addClass("simple-time-tracker-container");
|
||||||
// add start/stop controls
|
// add start/stop controls
|
||||||
let running = isRunning(tracker);
|
let running = isRunning(tracker);
|
||||||
|
@ -83,7 +83,7 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, file: str
|
||||||
createEl("th"));
|
createEl("th"));
|
||||||
|
|
||||||
for (let entry of orderedEntries(tracker.entries, settings))
|
for (let entry of orderedEntries(tracker.entries, settings))
|
||||||
addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, file, getSectionInfo, settings, 0);
|
addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, file, getSectionInfo, settings, 0, component);
|
||||||
|
|
||||||
// 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"});
|
||||||
|
@ -303,6 +303,80 @@ function orderedEntries(entries: Entry[], settings: SimpleTimeTrackerSettings):
|
||||||
return settings.reverseSegmentOrder ? entries.slice().reverse() : entries;
|
return settings.reverseSegmentOrder ? entries.slice().reverse() : entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableElement, newSegmentNameBox: TextComponent, trackerRunning: boolean, file: string, 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, file, 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, file, 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, file, getSectionInfo());
|
||||||
|
editButton.setIcon("lucide-pencil");
|
||||||
|
|
||||||
|
renderNameAsMarkdown(nameField.label, file, 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 () => {
|
||||||
|
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, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNameAsMarkdown(label: HTMLSpanElement, path: string, component: Component): void {
|
||||||
|
void MarkdownRenderer.renderMarkdown(label.innerHTML, label, path, component);
|
||||||
|
// rendering wraps it in a paragraph
|
||||||
|
label.innerHTML = label.querySelector("p").innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class EditableField {
|
class EditableField {
|
||||||
cell: HTMLTableCellElement;
|
cell: HTMLTableCellElement;
|
||||||
label: HTMLSpanElement;
|
label: HTMLSpanElement;
|
||||||
|
@ -369,86 +443,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) : ""});
|
|
||||||
|
|
||||||
renderSegments(row, file);
|
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
renderSegments(row, file);
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render Segment as Markdown
|
|
||||||
* @param row - Html row in table
|
|
||||||
* @param path - Path to file with time tracker
|
|
||||||
*/
|
|
||||||
function renderSegments(row: any, path: string) {
|
|
||||||
// Get coluumn with Segment
|
|
||||||
const segment = row.querySelector("td:first-child span");
|
|
||||||
if (segment) {
|
|
||||||
const htmlData = segment.innerHTML;
|
|
||||||
// Render Markdown
|
|
||||||
// Result `<p>_rendered_html_</p>`
|
|
||||||
MarkdownRenderer.renderMarkdown(htmlData, segment as HTMLElement, path, this);
|
|
||||||
// Replace current segment by rendered version
|
|
||||||
segment.innerHTML = segment.querySelector("p").innerHTML;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Tested for #tag, *italic*, [link](test2), etc:
|
Tested for #tag, *italic*, [link](test2), etc:
|
||||||
```simple-time-tracker
|
```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}]}
|
{"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}]}
|
||||||
```
|
```
|
||||||
|
|
|
@ -9,5 +9,6 @@
|
||||||
"0.1.6": "0.15.0",
|
"0.1.6": "0.15.0",
|
||||||
"0.1.7": "1.2.8",
|
"0.1.7": "1.2.8",
|
||||||
"0.1.8": "1.3.0",
|
"0.1.8": "1.3.0",
|
||||||
"0.2.0": "1.3.0"
|
"0.2.0": "1.3.0",
|
||||||
|
"0.2.1": "1.3.0"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue