added the ability to copy the table as markdown or csv

This commit is contained in:
Ell 2022-09-28 14:09:21 +02:00
parent 10251de71c
commit 90c6b55ad7
9 changed files with 191 additions and 51 deletions

View file

@ -17,7 +17,6 @@ The tracker's information is stored in the code block as JSON data. The names, s
Super Simple Time Tracker is still in its early stages! There are a lot of plans for it, including: 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 - A setting to link segments to corresponding daily notes automatically
- A neat interface to edit previous segments' names and time stamps - A neat interface to edit previous segments' names and time stamps
- The ability to copy the table in various formats, including as text, markdown, and csv
- A fancier Start and End button - A fancier Start and End button
# 🙏 Acknowledgements # 🙏 Acknowledgements

View file

@ -20,7 +20,7 @@ export class SimpleTimeTrackerSettingsTab extends PluginSettingTab {
.setDesc(createFragment(f => { .setDesc(createFragment(f => {
f.createSpan({ text: "The way that timestamps in time tracker tables should be displayed. Uses " }); 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.createEl("a", { text: "moment.js", href: "https://momentjs.com/docs/#/parsing/string-format/" });
f.createSpan({ text: " syntax. Clear to reset to default." }); f.createSpan({ text: " syntax." });
})) }))
.addText(t => { .addText(t => {
t.setValue(String(this.plugin.settings.timestampFormat)); t.setValue(String(this.plugin.settings.timestampFormat));
@ -30,6 +30,17 @@ export class SimpleTimeTrackerSettingsTab extends PluginSettingTab {
}); });
}); });
new 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(async v => {
this.plugin.settings.csvDelimiter = v.length ? v : defaultSettings.csvDelimiter;
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,9 +1,11 @@
export const defaultSettings: SimpleTimeTrackerSettings = { export const defaultSettings: SimpleTimeTrackerSettings = {
timestampFormat: "YY-MM-DD hh:mm:ss" timestampFormat: "YY-MM-DD hh:mm:ss",
csvDelimiter: ","
}; };
export interface SimpleTimeTrackerSettings { export interface SimpleTimeTrackerSettings {
timestampFormat: string; timestampFormat: string;
csvDelimiter: string;
} }

View file

@ -81,27 +81,36 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, getSectio
let total = totalDiv.createEl("span", { cls: "simple-time-tracker-timer-time", text: "0s" }); let total = totalDiv.createEl("span", { cls: "simple-time-tracker-timer-time", text: "0s" });
totalDiv.createEl("span", { text: "Total" }); totalDiv.createEl("span", { text: "Total" });
// add table
if (tracker.entries.length > 0) { 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( table.createEl("tr").append(
createEl("th", { text: "Segment" }), createEl("th", { text: "Segment" }),
createEl("th", { text: "Start time" }), createEl("th", { text: "Start time" }),
createEl("th", { text: "End time" }), createEl("th", { text: "End time" }),
createEl("th", { text: "Total" })); createEl("th", { text: "Duration" }));
for (let entry of tracker.entries) { for (let entry of tracker.entries) {
let row = table.createEl("tr"); let row = table.createEl("tr");
row.createEl("td", { text: entry.name }); row.createEl("td", { text: entry.name });
row.createEl("td", { text: moment.unix(entry.startTime).format(settings.timestampFormat) }); row.createEl("td", { text: formatTimestamp(entry.startTime, settings) });
if (entry.endTime) { if (entry.endTime) {
row.createEl("td", { text: moment.unix(entry.endTime).format(settings.timestampFormat) }); row.createEl("td", { text: formatTimestamp(entry.endTime, settings) });
let duration = moment.unix(entry.endTime).diff(moment.unix(entry.startTime)); row.createEl("td", { text: formatDurationBetween(entry.startTime, entry.endTime) });
row.createEl("td", { text: getCountdownDisplay(moment.duration(duration)) });
} }
} }
// add copy buttons
let buttons = element.createEl("div", { cls: "simple-time-tracker-bottom" });
new ButtonComponent(buttons)
.setButtonText("Copy as table")
.onClick(() => navigator.clipboard.writeText(createMarkdownTable(tracker, settings)));
new ButtonComponent(buttons)
.setButtonText("Copy as csv")
.onClick(() => navigator.clipboard.writeText(createCsv(tracker, settings)));
} }
setCountdownValues(tracker, current, total, currentDiv); setCountdownValues(tracker, current, total, currentDiv);
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
@ -113,7 +122,35 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, getSectio
}, 1000); }, 1000);
} }
function getCountdownDisplay(duration: moment.Duration): string { 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)));
}
currentDiv.hidden = !currEntry || !!currEntry.endTime;
}
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));
}
return totalDuration;
}
function formatTimestamp(timestamp: number, settings: SimpleTimeTrackerSettings): string {
return moment.unix(timestamp).format(settings.timestampFormat);
}
function formatDurationBetween(startTime: number, endTime: number): string {
return formatDuration(moment.unix(endTime).diff(moment.unix(startTime)));
}
function formatDuration(totalTime: number): string {
let duration = moment.duration(totalTime);
let ret = ""; let ret = "";
if (duration.hours() > 0) if (duration.hours() > 0)
ret += duration.hours() + "h "; ret += duration.hours() + "h ";
@ -123,20 +160,39 @@ function getCountdownDisplay(duration: moment.Duration): string {
return ret; return ret;
} }
function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLElement, currentDiv: HTMLDivElement) { function createMarkdownTable(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
let currEntry = tracker.entries.last(); let table = [["Segment", "Start time", "End time", "Duration"]];
if (currEntry) { for (let entry of tracker.entries)
if (!currEntry.endTime) { table.push(createTableRow(entry, settings));
let currDuration = moment().diff(moment.unix(currEntry.startTime)); table.push(["**Total**", "", "", `**${formatDuration(getTotalDuration(tracker))}**`]);
current.setText(getCountdownDisplay(moment.duration(currDuration)));
}
let totalDuration = 0; let ret = "";
for (let entry of tracker.entries) { // calculate the width every column needs to look neat when monospaced
let endTime = entry.endTime ? moment.unix(entry.endTime) : moment(); let widths = Array.from(Array(4).keys()).map(i => Math.max(...table.map(a => a[i].length)));
totalDuration += endTime.diff(moment.unix(entry.startTime)); for (let r = 0; r < table.length; r++) {
} // add separators after first row
total.setText(getCountdownDisplay(moment.duration(totalDuration))); if (r == 1)
ret += Array.from(Array(4).keys()).map(i => "-".repeat(widths[i])).join(" | ") + "\n";
let row: string[] = [];
for (let i = 0; i < 4; i++)
row.push(table[r][i].padEnd(widths[i], " "));
ret += row.join(" | ") + "\n";
} }
currentDiv.hidden = !currEntry || !!currEntry.endTime; return ret;
}
function createCsv(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
let ret = "";
for (let entry of tracker.entries)
ret += createTableRow(entry, settings).join(settings.csvSeparator) + "\n";
return ret;
}
function createTableRow(entry: Entry, settings: SimpleTimeTrackerSettings): string[] {
return [
entry.name,
formatTimestamp(entry.startTime, settings),
entry.endTime ? formatTimestamp(entry.endTime, settings) : "",
entry.endTime ? formatDurationBetween(entry.startTime, entry.endTime) : ""];
} }

View file

@ -20,7 +20,12 @@
margin-bottom: 10px; margin-bottom: 10px;
} }
.simple-time-tracker-timers { .simple-time-tracker-bottom button {
margin: 10px 5px 10px 5px;
}
.simple-time-tracker-timers,
.simple-time-tracker-bottom {
display: flex; display: flex;
justify-content: center; justify-content: center;
text-align: center; text-align: center;

View file

@ -1,3 +1,4 @@
{ {
"timestampFormat": "YY-MM-DD hh:mm:ss" "timestampFormat": "YY-MM-DD hh:mm:ss",
"csvSeparator": ","
} }

File diff suppressed because one or more lines are too long

View file

@ -20,7 +20,12 @@
margin-bottom: 10px; margin-bottom: 10px;
} }
.simple-time-tracker-timers { .simple-time-tracker-bottom button {
margin: 10px 5px 10px 5px;
}
.simple-time-tracker-timers,
.simple-time-tracker-bottom {
display: flex; display: flex;
justify-content: center; justify-content: center;
text-align: center; text-align: center;

View file

@ -2,5 +2,17 @@
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":"Think about project","startTime":1664305777,"endTime":1664308788},{"name":"Create project note","startTime":1664308810,"endTime":1664308815},{"name":"Work on project","startTime":1664308830,"endTime":1664309301}]} {"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}]}
``` ```
```
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
```