mirror of
https://github.com/Ellpeck/ObsidianSimpleTimeTracker.git
synced 2024-09-27 13:01:06 +02:00
Compare commits
59 commits
Author | SHA1 | Date | |
---|---|---|---|
7033f28555 | |||
993a7a4995 | |||
7b189a493a | |||
ea4f51b1e9 | |||
321f0178d4 | |||
07c083d63f | |||
d80bc764ce | |||
0ca60318f8 | |||
6820640a4f | |||
41d595201d | |||
ed0ad218b9 | |||
|
1ddefa51f4 | ||
9b718d2cae | |||
501cc50bf6 | |||
cbba377c60 | |||
|
5b79425a50 | ||
7f75b08e4d | |||
ebecd57b91 | |||
3186f1ba4d | |||
ea04e198d9 | |||
|
bf9ac5636c | ||
6823de48a0 | |||
|
70cb1594ef | ||
66eb45d253 | |||
4b6abebe61 | |||
4c88c3282d | |||
|
739e51326f | ||
48d576aaa2 | |||
701be28601 | |||
1d90a17e69 | |||
7633ac8144 | |||
7e108a85cb | |||
5bd9fefe60 | |||
3469232d3b | |||
92b2a287aa | |||
51d1183c0f | |||
17471e2831 | |||
e95f62fb9d | |||
f766fa7210 | |||
3be8d8de02 | |||
|
a6727bb879 | ||
|
3d9e6163e7 | ||
|
0612a4bd21 | ||
|
5677e02243 | ||
|
94631bc293 | ||
941c5e261b | |||
b5e768d99c | |||
175ef1e0d7 | |||
|
5baf96571e | ||
7aa91901c6 | |||
70ade8ada0 | |||
05a4710b8a | |||
e7815334b2 | |||
|
967d331172 | ||
|
85ee96f5dd | ||
402b97c799 | |||
1bda2accf4 | |||
132e2088be | |||
bce221aaaa |
31 changed files with 2688 additions and 1994 deletions
|
@ -4,6 +4,6 @@ root = true
|
|||
[*]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -12,6 +12,9 @@ node_modules
|
|||
# They should be uploaded to GitHub releases instead.
|
||||
/main.js
|
||||
|
||||
# Exclude local settings
|
||||
data.json
|
||||
|
||||
# Exclude sourcemaps
|
||||
*.map
|
||||
|
||||
|
|
40
README.md
40
README.md
|
@ -8,16 +8,46 @@ 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)
|
||||
|
||||
## 🔍 Tracker Data in Dataview
|
||||
Super Simple Time Tracker has a public API that can be used with [Dataview](https://blacksmithgu.github.io/obsidian-dataview/), specifically [DataviewJS](https://blacksmithgu.github.io/obsidian-dataview/api/intro/), which can be accessed using the following code:
|
||||
|
||||
```js
|
||||
dv.app.plugins.plugins["simple-time-tracker"].api;
|
||||
```
|
||||
|
||||
The following is a short example that uses DataviewJS to load all trackers in the vault and print the total duration of each tracker. You can also find this example in action [in the test vault](https://github.com/Ellpeck/ObsidianSimpleTimeTracker/blob/master/test-vault/dataview-test.md?plain=1).
|
||||
|
||||
```js
|
||||
// get the time tracker plugin api instance
|
||||
let api = dv.app.plugins.plugins["simple-time-tracker"].api;
|
||||
|
||||
for (let page of dv.pages()) {
|
||||
// load trackers in the file with the given path
|
||||
let trackers = await api.loadAllTrackers(page.file.path);
|
||||
|
||||
if (trackers.length)
|
||||
dv.el("strong", "Trackers in " + page.file.name);
|
||||
|
||||
for (let { section, tracker } of trackers) {
|
||||
// print the total duration of the tracker
|
||||
let duration = api.getTotalDuration(tracker.entries);
|
||||
dv.el("p", api.formatDuration(duration));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A full list of the functions exposed through the API can be found [in the code](https://github.com/Ellpeck/ObsidianSimpleTimeTracker/blob/master/src/main.ts#L8-L16). Proper documentation for the API will be added in the future.
|
||||
|
||||
# 👀 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)
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"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": "1.0.3",
|
||||
"minAppVersion": "1.2.8",
|
||||
"description": "Multi-purpose time trackers for your notes!",
|
||||
"author": "Ellpeck",
|
||||
"authorUrl": "https://ellpeck.de",
|
||||
"fundingUrl": "https://ellpeck.de/support",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
3027
package-lock.json
generated
3027
package-lock.json
generated
File diff suppressed because it is too large
Load diff
44
package.json
44
package.json
|
@ -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": "1.0.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"
|
||||
}
|
||||
}
|
||||
|
|
48
src/confirm-modal.ts
Normal file
48
src/confirm-modal.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { App, Modal, Setting } from "obsidian";
|
||||
|
||||
export class ConfirmModal extends Modal {
|
||||
// Message to show in the modal
|
||||
message: string;
|
||||
|
||||
// Callback to run on user choice
|
||||
callback: (choice: boolean) => void;
|
||||
|
||||
// Whether an option was picked
|
||||
picked: boolean;
|
||||
|
||||
constructor(app: App, message: string, callback: (choice: boolean) => void) {
|
||||
super(app);
|
||||
this.message = message;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
const { contentEl } = this;
|
||||
contentEl.createEl("p", { text: this.message });
|
||||
|
||||
new Setting(contentEl)
|
||||
.addButton((btn) =>
|
||||
btn
|
||||
.setButtonText("Ok")
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.picked = true;
|
||||
this.close();
|
||||
this.callback(true);
|
||||
})
|
||||
)
|
||||
.addButton((btn) =>
|
||||
btn.setButtonText("Cancel").onClick(() => {
|
||||
this.picked = true;
|
||||
this.close();
|
||||
this.callback(false);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
if (!this.picked) {
|
||||
this.callback(false);
|
||||
}
|
||||
}
|
||||
}
|
73
src/main.ts
73
src/main.ts
|
@ -1,37 +1,60 @@
|
|||
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";
|
||||
import { displayTracker, Entry, formatDuration, formatTimestamp, getDuration, getRunningEntry, getTotalDuration, isRunning, loadAllTrackers, loadTracker, orderedEntries } from "./tracker";
|
||||
|
||||
export default class SimpleTimeTrackerPlugin extends Plugin {
|
||||
|
||||
settings: SimpleTimeTrackerSettings;
|
||||
public api = {
|
||||
// verbatim versions of the functions found in tracker.ts with the same parameters
|
||||
loadTracker, loadAllTrackers, getDuration, getTotalDuration, getRunningEntry, isRunning,
|
||||
|
||||
async onload(): Promise<void> {
|
||||
await this.loadSettings();
|
||||
// modified versions of the functions found in tracker.ts, with the number of required arguments reduced
|
||||
formatTimestamp: (timestamp: string) => formatTimestamp(timestamp, this.settings),
|
||||
formatDuration: (totalTime: number) => formatDuration(totalTime, this.settings),
|
||||
orderedEntries: (entries: Entry[]) => orderedEntries(entries, this.settings)
|
||||
};
|
||||
public settings: SimpleTimeTrackerSettings;
|
||||
|
||||
this.addSettingTab(new SimpleTimeTrackerSettingsTab(this.app, this));
|
||||
async onload(): Promise<void> {
|
||||
await this.loadSettings();
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor("simple-time-tracker", (s, e, i) => {
|
||||
let tracker = loadTracker(s);
|
||||
e.empty();
|
||||
displayTracker(tracker, e, () => i.getSectionInfo(e), this.settings);
|
||||
});
|
||||
this.addSettingTab(new SimpleTimeTrackerSettingsTab(this.app, this));
|
||||
|
||||
this.addCommand({
|
||||
id: `insert`,
|
||||
name: `Insert Time Tracker`,
|
||||
editorCallback: (e, _) => {
|
||||
e.replaceSelection("```simple-time-tracker\n```\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
this.registerMarkdownCodeBlockProcessor("simple-time-tracker", (s, e, i) => {
|
||||
e.empty();
|
||||
let component = new MarkdownRenderChild(e);
|
||||
let tracker = loadTracker(s);
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, defaultSettings, await this.loadData());
|
||||
}
|
||||
// Wrap file name in a function since it can change
|
||||
let filePath = i.sourcePath;
|
||||
const getFile = () => filePath;
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
// Hook rename events to update the file path
|
||||
component.registerEvent(this.app.vault.on("rename", (file, oldPath) => {
|
||||
if (file instanceof TFile && oldPath === filePath) {
|
||||
filePath = file.path;
|
||||
}
|
||||
}));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,49 @@ 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: "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.png" }, cls: "simple-time-tracker-support" });
|
||||
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"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
515
src/tracker.ts
515
src/tracker.ts
|
@ -1,5 +1,6 @@
|
|||
import { moment, App, MarkdownSectionInformation, ButtonComponent, TextComponent } from "obsidian";
|
||||
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[];
|
||||
|
@ -7,29 +8,14 @@ export interface Tracker {
|
|||
|
||||
export interface Entry {
|
||||
name: string;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
subEntries?: Entry[];
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
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, 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,7 +33,9 @@ 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);
|
||||
updateLegacyInfo(ret.entries);
|
||||
return ret;
|
||||
} catch (e) {
|
||||
console.log(`Failed to parse Tracker from ${json}`);
|
||||
}
|
||||
|
@ -55,7 +43,35 @@ export function loadTracker(json: string): Tracker {
|
|||
return { entries: [] };
|
||||
}
|
||||
|
||||
export function displayTracker(tracker: Tracker, element: HTMLElement, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings): void {
|
||||
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 trackers: { section: MarkdownSectionInformation, tracker: Tracker }[] = [];
|
||||
let curr: Partial<MarkdownSectionInformation> | undefined;
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
let line = content[i];
|
||||
if (line.trimEnd() == "```simple-time-tracker") {
|
||||
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;
|
||||
} else {
|
||||
curr.text += `${line}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return trackers;
|
||||
}
|
||||
|
||||
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,17 +80,17 @@ 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, 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" });
|
||||
|
@ -95,46 +111,8 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, getSectio
|
|||
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" });
|
||||
|
@ -147,60 +125,58 @@ 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)));
|
||||
export 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));
|
||||
}
|
||||
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 = "";
|
||||
if (duration.hours() > 0)
|
||||
ret += duration.hours() + "h ";
|
||||
if (duration.minutes() > 0)
|
||||
ret += duration.minutes() + "m ";
|
||||
ret += duration.seconds() + "s";
|
||||
export function getTotalDuration(entries: Entry[]): number {
|
||||
let ret = 0;
|
||||
for (let entry of entries)
|
||||
ret += getDuration(entry);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function createMarkdownTable(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
|
||||
export function isRunning(tracker: Tracker): boolean {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
export 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 +184,330 @@ 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 {
|
||||
export 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 [
|
||||
entry.name,
|
||||
formatTimestamp(entry.startTime, settings),
|
||||
entry.endTime ? formatTimestamp(entry.endTime, settings) : "",
|
||||
entry.endTime ? formatDurationBetween(entry.startTime, entry.endTime) : ""];
|
||||
export function orderedEntries(entries: Entry[], settings: SimpleTimeTrackerSettings): Entry[] {
|
||||
return settings.reverseSegmentOrder ? entries.slice().reverse() : entries;
|
||||
}
|
||||
|
||||
export function formatTimestamp(timestamp: string, settings: SimpleTimeTrackerSettings): string {
|
||||
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());
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
if (!name)
|
||||
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);
|
||||
}
|
||||
|
||||
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 = undefined;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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 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 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();
|
||||
if (entry.endTime && !isNaN(+entry.endTime))
|
||||
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;
|
||||
|
||||
if (entry.subEntries)
|
||||
updateLegacyInfo(entry.subEntries);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createTableSection(entry: Entry, settings: SimpleTimeTrackerSettings): string[][] {
|
||||
let ret = [[
|
||||
entry.name,
|
||||
entry.startTime ? formatTimestamp(entry.startTime, settings) : "",
|
||||
entry.endTime ? formatTimestamp(entry.endTime, 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));
|
||||
}
|
||||
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 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 expandButton = new ButtonComponent(nameField.label)
|
||||
.setClass("clickable-icon")
|
||||
.setClass("simple-time-tracker-expand-button")
|
||||
.setIcon(`chevron-${entry.collapsed ? "left" : "down"}`)
|
||||
.onClick(async () => {
|
||||
if (entry.collapsed) {
|
||||
entry.collapsed = undefined;
|
||||
} else {
|
||||
entry.collapsed = true;
|
||||
}
|
||||
await saveTracker(tracker, getFile(), getSectionInfo());
|
||||
});
|
||||
if (!entry.subEntries)
|
||||
expandButton.buttonEl.style.visibility = "hidden";
|
||||
|
||||
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());
|
||||
});
|
||||
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();
|
||||
if (!entryRunning) {
|
||||
endField.endEdit();
|
||||
entry.endTime = endField.getTimestamp();
|
||||
}
|
||||
await saveTracker(tracker, getFile(), getSectionInfo());
|
||||
editButton.setIcon("lucide-pencil");
|
||||
|
||||
renderNameAsMarkdown(nameField.label, getFile, component);
|
||||
} else {
|
||||
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);
|
||||
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 () => {
|
||||
|
||||
const confirmed = await showConfirm("Are you sure you want to delete this entry?");
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function showConfirm(message: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
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);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
70
styles.css
70
styles.css
|
@ -1,64 +1,86 @@
|
|||
.simple-time-tracker-support {
|
||||
max-width: 50%;
|
||||
width: 400px;
|
||||
height: auto;
|
||||
.simple-time-tracker-container {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.simple-time-tracker-settings-image {
|
||||
width: 100%;
|
||||
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-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.simple-time-tracker-expand-button {
|
||||
margin-inline-start: 0.5em;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
|
3
test-vault/.obsidian/community-plugins.json
vendored
3
test-vault/.obsidian/community-plugins.json
vendored
|
@ -1,3 +1,4 @@
|
|||
[
|
||||
"simple-time-tracker"
|
||||
"simple-time-tracker",
|
||||
"dataview"
|
||||
]
|
30
test-vault/.obsidian/core-plugins-migration.json
vendored
Normal file
30
test-vault/.obsidian/core-plugins-migration.json
vendored
Normal 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
|
||||
}
|
49
test-vault/.obsidian/core-plugins.json
vendored
49
test-vault/.obsidian/core-plugins.json
vendored
|
@ -1,19 +1,30 @@
|
|||
[
|
||||
"file-explorer",
|
||||
"global-search",
|
||||
"switcher",
|
||||
"graph",
|
||||
"backlink",
|
||||
"outgoing-link",
|
||||
"tag-pane",
|
||||
"page-preview",
|
||||
"daily-notes",
|
||||
"templates",
|
||||
"note-composer",
|
||||
"command-palette",
|
||||
"editor-status",
|
||||
"starred",
|
||||
"outline",
|
||||
"word-count",
|
||||
"file-recovery"
|
||||
]
|
||||
{
|
||||
"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
|
||||
}
|
3
test-vault/.obsidian/plugins/dataview/.gitignore
vendored
Normal file
3
test-vault/.obsidian/plugins/dataview/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*
|
||||
!.gitignore
|
||||
!data.json
|
27
test-vault/.obsidian/plugins/dataview/data.json
vendored
Normal file
27
test-vault/.obsidian/plugins/dataview/data.json
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"renderNullAs": "\\-",
|
||||
"taskCompletionTracking": false,
|
||||
"taskCompletionUseEmojiShorthand": false,
|
||||
"taskCompletionText": "completion",
|
||||
"taskCompletionDateFormat": "yyyy-MM-dd",
|
||||
"recursiveSubTaskCompletion": false,
|
||||
"warnOnEmptyResult": true,
|
||||
"refreshEnabled": true,
|
||||
"refreshInterval": 2500,
|
||||
"defaultDateFormat": "MMMM dd, yyyy",
|
||||
"defaultDateTimeFormat": "h:mm a - MMMM dd, yyyy",
|
||||
"maxRecursiveRenderDepth": 4,
|
||||
"tableIdColumnName": "File",
|
||||
"tableGroupColumnName": "Group",
|
||||
"showResultCount": true,
|
||||
"allowHtml": true,
|
||||
"inlineQueryPrefix": "=",
|
||||
"inlineJsQueryPrefix": "$=",
|
||||
"inlineQueriesInCodeblocks": true,
|
||||
"enableInlineDataview": true,
|
||||
"enableDataviewJs": true,
|
||||
"enableInlineDataviewJs": true,
|
||||
"prettyRenderInlineFields": true,
|
||||
"prettyRenderInlineFieldsInLivePreview": true,
|
||||
"dataviewJsKeyword": "dataviewjs"
|
||||
}
|
3
test-vault/.obsidian/plugins/simple-time-tracker/.gitignore
vendored
Normal file
3
test-vault/.obsidian/plugins/simple-time-tracker/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*
|
||||
!.gitignore
|
||||
!data.json
|
|
@ -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
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
18
test-vault/dataview-test.md
Normal file
18
test-vault/dataview-test.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
```dataviewjs
|
||||
// get the time tracker plugin api instance
|
||||
let api = dv.app.plugins.plugins["simple-time-tracker"].api;
|
||||
|
||||
for(let page of dv.pages()) {
|
||||
// load trackers in the file with the given path
|
||||
let trackers = await api.loadAllTrackers(page.file.path);
|
||||
|
||||
if (trackers.length)
|
||||
dv.el("strong", "Trackers in " + page.file.name);
|
||||
|
||||
for (let {section, tracker} of trackers) {
|
||||
// print the total duration of the tracker
|
||||
let duration = api.getTotalDuration(tracker.entries);
|
||||
dv.el("p", api.formatDuration(duration))
|
||||
}
|
||||
}
|
||||
```
|
10
test-vault/duration_accumulation_test.md
Normal file
10
test-vault/duration_accumulation_test.md
Normal 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}]}]}
|
||||
```
|
5
test-vault/test-markdown.md
Normal file
5
test-vault/test-markdown.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
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"},{"name":"Part 3","startTime":"2024-03-17T11:17:08.000Z","endTime":"2024-03-17T11:17:24.000Z"}]},{"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"},{"name":"#tag5 Part 2 *italic*","startTime":"2024-03-17T12:22:20.000Z","endTime":"2024-03-17T12:22:24.000Z"}]},{"name":"*italic* Segment 5 #tag6 [test2](test2)","startTime":"2024-03-17T12:40:37.000Z","endTime":"2024-03-17T12:40:45.000Z"},{"name":"Segment 6","startTime":"2024-03-27T13:20:56.000Z","endTime":"2024-08-09T16:27:18.029Z"}]}
|
||||
```
|
||||
|
6
test-vault/test/Cool Project.md
Normal file
6
test-vault/test/Cool Project.md
Normal 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}]}
|
||||
```
|
6
test-vault/test/Untitled.canvas
Normal file
6
test-vault/test/Untitled.canvas
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"nodes":[
|
||||
{"type":"file","file":"test/Cool Project.md","id":"e41a2deb229880a8","x":-720,"y":-620,"width":1120,"height":1000}
|
||||
],
|
||||
"edges":[]
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,19 @@
|
|||
{
|
||||
"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",
|
||||
"0.2.2": "1.3.0",
|
||||
"1.0.0": "1.3.0",
|
||||
"1.0.1": "1.3.0",
|
||||
"1.0.2": "1.3.0",
|
||||
"1.0.3": "1.3.0"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue