This commit is contained in:
hen 2024-11-14 00:53:38 -03:00 committed by GitHub
commit 094c53e4f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,327 +1,358 @@
import { moment, MarkdownSectionInformation, ButtonComponent, TextComponent, TFile, MarkdownRenderer, Component, MarkdownRenderChild } from "obsidian";
import { SimpleTimeTrackerSettings } from "./settings";
import { ConfirmModal } from "./confirm-modal";
import { moment, MarkdownSectionInformation, ButtonComponent, TextComponent, TFile, MarkdownRenderer, Component, MarkdownRenderChild } from "obsidian"
import { SimpleTimeTrackerSettings } from "./settings"
import { ConfirmModal } from "./confirm-modal"
export interface Tracker {
entries: Entry[];
entries: Entry[]
}
export interface Entry {
name: string;
startTime: string;
endTime: string;
subEntries?: Entry[];
collapsed?: boolean;
name: string
startTime: string
endTime: string
subEntries?: Entry[]
collapsed?: boolean
}
export async function saveTracker(tracker: Tracker, fileName: string, section: MarkdownSectionInformation): Promise<void> {
let file = app.vault.getAbstractFileByPath(fileName) as TFile;
let file = app.vault.getAbstractFileByPath(fileName) as TFile
if (!file)
return;
let content = await app.vault.read(file);
return
let content = await app.vault.read(file)
// figure out what part of the content we have to edit
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");
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")
// edit only the code block content, leave the rest untouched
content = `${prev}\n${JSON.stringify(tracker)}\n${next}`;
content = `${prev}\n${JSON.stringify(tracker)}\n${next}`
await app.vault.modify(file, content);
await app.vault.modify(file, content)
}
export function loadTracker(json: string): Tracker {
if (json) {
try {
let ret = JSON.parse(json);
updateLegacyInfo(ret.entries);
return ret;
let ret = JSON.parse(json)
updateLegacyInfo(ret.entries)
return ret
} catch (e) {
console.log(`Failed to parse Tracker from ${json}`);
console.log(`Failed to parse Tracker from ${json}`)
}
}
return { entries: [] };
return { entries: [] }
}
export async function loadAllTrackers(fileName: string): Promise<{ section: MarkdownSectionInformation, tracker: Tracker }[]> {
let file = app.vault.getAbstractFileByPath(fileName);
let content = (await app.vault.cachedRead(file as TFile)).split("\n");
let file = app.vault.getAbstractFileByPath(fileName)
let content = (await app.vault.cachedRead(file as TFile)).split("\n")
let trackers: { section: MarkdownSectionInformation, tracker: Tracker }[] = [];
let curr: Partial<MarkdownSectionInformation> | undefined;
let trackers: { section: MarkdownSectionInformation, tracker: Tracker }[] = []
let curr: Partial<MarkdownSectionInformation> | undefined
for (let i = 0; i < content.length; i++) {
let line = content[i];
let line = content[i]
if (line.trimEnd() == "```simple-time-tracker") {
curr = { lineStart: i + 1, text: "" };
curr = { lineStart: i + 1, text: "" }
} else if (curr) {
if (line.trimEnd() == "```") {
curr.lineEnd = i - 1;
let tracker = loadTracker(curr.text);
trackers.push({ section: curr as MarkdownSectionInformation, tracker: tracker });
curr = undefined;
curr.lineEnd = i - 1
let tracker = loadTracker(curr.text)
trackers.push({ section: curr as MarkdownSectionInformation, tracker: tracker })
curr = undefined
} else {
curr.text += `${line}\n`;
curr.text += `${line}\n`
}
}
}
return trackers;
return trackers
}
type GetFile = () => string;
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");
element.addClass("simple-time-tracker-container")
// add start/stop controls
let running = isRunning(tracker);
let running = isRunning(tracker)
let btn = new ButtonComponent(element)
.setClass("clickable-icon")
.setIcon(`lucide-${running ? "stop" : "play"}-circle`)
.setTooltip(running ? "End" : "Start")
.onClick(async () => {
if (running) {
endRunningEntry(tracker);
endRunningEntry(tracker)
} else {
startNewEntry(tracker, newSegmentNameBox.getValue());
startNewEntry(tracker, newSegmentNameBox.getValue())
}
await saveTracker(tracker, getFile(), getSectionInfo());
});
btn.buttonEl.addClass("simple-time-tracker-btn");
await saveTracker(tracker, getFile(), getSectionInfo())
})
btn.buttonEl.addClass("simple-time-tracker-btn")
let newSegmentNameBox = new TextComponent(element)
.setPlaceholder("Segment name")
.setDisabled(running);
newSegmentNameBox.inputEl.addClass("simple-time-tracker-txt");
.setDisabled(running)
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" })
let totalTodayDiv = timer.createEl("div", { cls: "simple-time-tracker-timer" })
let totalToday = totalTodayDiv.createEl("span", { cls: "simple-time-tracker-timer-time", text: "0s" })
totalTodayDiv.createEl("span", { text: "Total Today" })
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"));
createEl("th"))
for (let entry of orderedEntries(tracker.entries, settings))
addEditableTableRow(tracker, entry, table, newSegmentNameBox, running, getFile, getSectionInfo, settings, 0, component);
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)));
.onClick(() => navigator.clipboard.writeText(createMarkdownTable(tracker, settings)))
new ButtonComponent(buttons)
.setButtonText("Copy as CSV")
.onClick(() => navigator.clipboard.writeText(createCsv(tracker, settings)));
.onClick(() => navigator.clipboard.writeText(createCsv(tracker, settings)))
}
setCountdownValues(tracker, current, total, currentDiv, settings);
setCountdownValues(tracker, current, total, totalToday, currentDiv, settings)
let intervalId = window.setInterval(() => {
// we delete the interval timer when the element is removed
if (!element.isConnected) {
window.clearInterval(intervalId);
return;
window.clearInterval(intervalId)
return
}
setCountdownValues(tracker, current, total, currentDiv, settings);
}, 1000);
setCountdownValues(tracker, current, total, totalToday, currentDiv, settings)
}, 1000)
}
export function getDuration(entry: Entry): number {
if (entry.subEntries) {
return getTotalDuration(entry.subEntries);
return getTotalDuration(entry.subEntries)
} else {
let endTime = entry.endTime ? moment(entry.endTime) : moment();
return endTime.diff(moment(entry.startTime));
let endTime = entry.endTime ? moment(entry.endTime) : moment()
return endTime.diff(moment(entry.startTime))
}
}
export function getDurationToday(entry: Entry): number {
if (entry.subEntries) {
return getTotalDurationToday(entry.subEntries)
} else {
let today = moment().startOf('day')
let endTime = entry.endTime ? moment(entry.endTime) : moment()
let startTime = moment(entry.startTime)
if (endTime.isBefore(today)) {
return 0
}
if (startTime.isBefore(today)) {
startTime = today
}
return endTime.diff(startTime)
}
}
export function getTotalDuration(entries: Entry[]): number {
let ret = 0;
let ret = 0
for (let entry of entries)
ret += getDuration(entry);
return ret;
ret += getDuration(entry)
return ret
}
export function getTotalDurationToday(entries: Entry[]): number {
let ret = 0
for (let entry of entries)
ret += getDurationToday(entry)
return ret
}
export function isRunning(tracker: Tracker): boolean {
return !!getRunningEntry(tracker.entries);
return !!getRunningEntry(tracker.entries)
}
export 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);
let running = getRunningEntry(entry.subEntries)
if (running)
return running;
return running
} else {
// if this entry has no sub entries and no end time, it's running
if (!entry.endTime)
return entry;
return entry
}
}
return null;
return null
}
export function createMarkdownTable(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
let table = [["Segment", "Start time", "End time", "Duration"]];
let table = [["Segment", "Start time", "End time", "Duration"]]
for (let entry of orderedEntries(tracker.entries, settings))
table.push(...createTableSection(entry, settings));
table.push(["**Total**", "", "", `**${formatDuration(getTotalDuration(tracker.entries), settings)}**`]);
table.push(...createTableSection(entry, settings))
table.push(["**Total**", "", "", `**${formatDuration(getTotalDuration(tracker.entries), settings)}**`])
let ret = "";
let ret = ""
// calculate the width every column needs to look neat when monospaced
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++) {
// 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[] = [];
let row: string[] = []
for (let i = 0; i < 4; i++)
row.push(table[r][i].padEnd(widths[i], " "));
ret += "| " + row.join(" | ") + " |\n";
row.push(table[r][i].padEnd(widths[i], " "))
ret += "| " + row.join(" | ") + " |\n"
}
return ret;
return ret
}
export function createCsv(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
let ret = "";
let ret = ""
for (let entry of orderedEntries(tracker.entries, settings)) {
for (let row of createTableSection(entry, settings))
ret += row.join(settings.csvDelimiter) + "\n";
ret += row.join(settings.csvDelimiter) + "\n"
}
return ret;
return ret
}
export function orderedEntries(entries: Entry[], settings: SimpleTimeTrackerSettings): Entry[] {
return settings.reverseSegmentOrder ? entries.slice().reverse() : entries;
return settings.reverseSegmentOrder ? entries.slice().reverse() : entries
}
export function formatTimestamp(timestamp: string, settings: SimpleTimeTrackerSettings): string {
return moment(timestamp).format(settings.timestampFormat);
return moment(timestamp).format(settings.timestampFormat)
}
export function formatDuration(totalTime: number, settings: SimpleTimeTrackerSettings): string {
let ret = "";
let duration = moment.duration(totalTime);
let hours = settings.fineGrainedDurations ? duration.hours() : Math.floor(duration.asHours());
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());
let days = Math.floor(duration.asDays())
if (days > 0)
ret += days + ".";
ret += days + "."
}
ret += `${hours.toString().padStart(2, "0")}:${duration.minutes().toString().padStart(2, "0")}:${duration.seconds().toString().padStart(2, "0")}`;
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());
let years = Math.floor(duration.asYears())
if (years > 0)
ret += years + "y ";
ret += years + "y "
if (duration.months() > 0)
ret += duration.months() + "M ";
ret += duration.months() + "M "
if (duration.days() > 0)
ret += duration.days() + "d ";
ret += duration.days() + "d "
}
if (hours > 0)
ret += hours + "h ";
ret += hours + "h "
if (duration.minutes() > 0)
ret += duration.minutes() + "m ";
ret += duration.seconds() + "s";
ret += duration.minutes() + "m "
ret += duration.seconds() + "s"
}
return ret;
return ret
}
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;
entry.subEntries = [{ ...entry, name: `Part 1` }]
entry.startTime = null
entry.endTime = null
}
if (!name)
name = `Part ${entry.subEntries.length + 1}`;
entry.subEntries.push({ name: name, startTime: moment().toISOString(), endTime: null, subEntries: undefined });
name = `Part ${entry.subEntries.length + 1}`
entry.subEntries.push({ name: name, startTime: moment().toISOString(), endTime: null, subEntries: undefined })
}
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: undefined };
tracker.entries.push(entry);
name = `Segment ${tracker.entries.length + 1}`
let entry: Entry = { name: name, startTime: moment().toISOString(), endTime: null, subEntries: undefined }
tracker.entries.push(entry)
}
function endRunningEntry(tracker: Tracker): void {
let entry = getRunningEntry(tracker.entries);
entry.endTime = moment().toISOString();
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;
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 = undefined;
let single = entry.subEntries[0]
entry.startTime = single.startTime
entry.endTime = single.endTime
entry.subEntries = undefined
}
return true;
return true
}
}
}
return false;
return false
}
function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLElement, currentDiv: HTMLDivElement, settings: SimpleTimeTrackerSettings): void {
let running = getRunningEntry(tracker.entries);
function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLElement, totalToday: HTMLElement, currentDiv: HTMLDivElement, settings: SimpleTimeTrackerSettings): void {
let running = getRunningEntry(tracker.entries)
if (running && !running.endTime) {
current.setText(formatDuration(getDuration(running), settings));
currentDiv.hidden = false;
current.setText(formatDuration(getDuration(running), settings))
currentDiv.hidden = false
} else {
currentDiv.hidden = true;
currentDiv.hidden = true
}
total.setText(formatDuration(getTotalDuration(tracker.entries), settings));
total.setText(formatDuration(getTotalDuration(tracker.entries), settings))
totalToday.setText(formatDuration(getTotalDurationToday(tracker.entries), settings))
}
function formatEditableTimestamp(timestamp: string, settings: SimpleTimeTrackerSettings): string {
return moment(timestamp).format(settings.editableTimestampFormat);
return moment(timestamp).format(settings.editableTimestampFormat)
}
function unformatEditableTimestamp(formatted: string, settings: SimpleTimeTrackerSettings): string {
return moment(formatted, settings.editableTimestampFormat).toISOString();
return moment(formatted, settings.editableTimestampFormat).toISOString()
}
function updateLegacyInfo(entries: Entry[]): void {
for (let entry of entries) {
// in 0.1.8, timestamps were changed from unix to iso
if (entry.startTime && !isNaN(+entry.startTime))
entry.startTime = moment.unix(+entry.startTime).toISOString();
entry.startTime = moment.unix(+entry.startTime).toISOString()
if (entry.endTime && !isNaN(+entry.endTime))
entry.endTime = moment.unix(+entry.endTime).toISOString();
entry.endTime = moment.unix(+entry.endTime).toISOString()
// in 1.0.0, sub-entries were made optional
if (entry.subEntries == null || !entry.subEntries.length)
entry.subEntries = undefined;
entry.subEntries = undefined
if (entry.subEntries)
updateLegacyInfo(entry.subEntries);
updateLegacyInfo(entry.subEntries)
}
}
@ -331,25 +362,25 @@ function createTableSection(entry: Entry, settings: SimpleTimeTrackerSettings):
entry.name,
entry.startTime ? formatTimestamp(entry.startTime, settings) : "",
entry.endTime ? formatTimestamp(entry.endTime, settings) : "",
entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : ""]];
entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : ""]]
if (entry.subEntries) {
for (let sub of orderedEntries(entry.subEntries, settings))
ret.push(...createTableSection(sub, settings));
ret.push(...createTableSection(sub, settings))
}
return ret;
return ret
}
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 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);
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) : "" });
row.createEl("td", { text: entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : "" })
renderNameAsMarkdown(nameField.label, getFile, component);
renderNameAsMarkdown(nameField.label, getFile, component)
let expandButton = new ButtonComponent(nameField.label)
.setClass("clickable-icon")
@ -357,56 +388,56 @@ function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableEle
.setIcon(`chevron-${entry.collapsed ? "left" : "down"}`)
.onClick(async () => {
if (entry.collapsed) {
entry.collapsed = undefined;
entry.collapsed = undefined
} else {
entry.collapsed = true;
entry.collapsed = true
}
await saveTracker(tracker, getFile(), getSectionInfo());
});
await saveTracker(tracker, getFile(), getSectionInfo())
})
if (!entry.subEntries)
expandButton.buttonEl.style.visibility = "hidden";
expandButton.buttonEl.style.visibility = "hidden"
let entryButtons = row.createEl("td");
entryButtons.addClass("simple-time-tracker-table-buttons");
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, getFile(), getSectionInfo());
});
startSubEntry(entry, newSegmentNameBox.getValue())
await saveTracker(tracker, getFile(), getSectionInfo())
})
let editButton = new ButtonComponent(entryButtons)
.setClass("clickable-icon")
.setTooltip("Edit")
.setIcon("lucide-pencil")
.onClick(async () => {
if (nameField.editing()) {
entry.name = nameField.endEdit();
expandButton.buttonEl.style.display = null;
startField.endEdit();
entry.startTime = startField.getTimestamp();
entry.name = nameField.endEdit()
expandButton.buttonEl.style.display = null
startField.endEdit()
entry.startTime = startField.getTimestamp()
if (!entryRunning) {
endField.endEdit();
entry.endTime = endField.getTimestamp();
endField.endEdit()
entry.endTime = endField.getTimestamp()
}
await saveTracker(tracker, getFile(), getSectionInfo());
editButton.setIcon("lucide-pencil");
await saveTracker(tracker, getFile(), getSectionInfo())
editButton.setIcon("lucide-pencil")
renderNameAsMarkdown(nameField.label, getFile, component);
renderNameAsMarkdown(nameField.label, getFile, component)
} else {
nameField.beginEdit(entry.name);
expandButton.buttonEl.style.display = "none";
nameField.beginEdit(entry.name)
expandButton.buttonEl.style.display = "none"
// only allow editing start and end times if we don't have sub entries
if (!entry.subEntries) {
startField.beginEdit(entry.startTime);
startField.beginEdit(entry.startTime)
if (!entryRunning)
endField.beginEdit(entry.endTime);
endField.beginEdit(entry.endTime)
}
editButton.setIcon("lucide-check");
editButton.setIcon("lucide-check")
}
});
})
new ButtonComponent(entryButtons)
.setClass("clickable-icon")
.setTooltip("Remove")
@ -414,100 +445,100 @@ function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableEle
.setDisabled(entryRunning)
.onClick(async () => {
const confirmed = await showConfirm("Are you sure you want to delete this entry?");
const confirmed = await showConfirm("Are you sure you want to delete this entry?")
if (!confirmed) {
return;
return
}
removeEntry(tracker.entries, entry);
await saveTracker(tracker, getFile(), getSectionInfo());
});
removeEntry(tracker.entries, entry)
await saveTracker(tracker, getFile(), getSectionInfo())
})
if (entry.subEntries && !entry.collapsed) {
for (let sub of orderedEntries(entry.subEntries, settings))
addEditableTableRow(tracker, sub, table, newSegmentNameBox, trackerRunning, getFile, getSectionInfo, settings, indent + 1, component);
addEditableTableRow(tracker, sub, table, newSegmentNameBox, trackerRunning, getFile, getSectionInfo, settings, indent + 1, component)
}
}
function showConfirm(message: string): Promise<boolean> {
return new Promise((resolve) => {
const modal = new ConfirmModal(app, message, resolve);
modal.open();
});
const modal = new ConfirmModal(app, message, resolve)
modal.open()
})
}
function renderNameAsMarkdown(label: HTMLSpanElement, getFile: GetFile, component: Component): void {
// we don't have to wait here since async code only occurs when a file needs to be loaded (like a linked image)
void MarkdownRenderer.renderMarkdown(label.innerHTML, label, getFile(), component);
void MarkdownRenderer.renderMarkdown(label.innerHTML, label, getFile(), component)
// rendering wraps it in a paragraph
label.innerHTML = label.querySelector("p").innerHTML;
label.innerHTML = label.querySelector("p").innerHTML
}
class EditableField {
cell: HTMLTableCellElement;
label: HTMLSpanElement;
box: TextComponent;
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();
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;
return this.label.hidden
}
beginEdit(value: string): void {
this.label.hidden = true;
this.box.setValue(value);
this.box.inputEl.show();
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;
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;
settings: SimpleTimeTrackerSettings
constructor(row: HTMLTableRowElement, value: string, settings: SimpleTimeTrackerSettings) {
super(row, 0, value ? formatTimestamp(value, settings) : "");
this.settings = settings;
super(row, 0, value ? formatTimestamp(value, settings) : "")
this.settings = settings
}
beginEdit(value: string): void {
super.beginEdit(value ? formatEditableTimestamp(value, this.settings) : "");
super.beginEdit(value ? formatEditableTimestamp(value, this.settings) : "")
}
endEdit(): string {
const value = this.box.getValue();
let displayValue = value;
const value = this.box.getValue()
let displayValue = value
if (value) {
const timestamp = unformatEditableTimestamp(value, this.settings);
displayValue = formatTimestamp(timestamp, this.settings);
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;
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);
return unformatEditableTimestamp(this.box.getValue(), this.settings)
} else {
return null;
return null
}
}
}