diff --git a/esbuild.config.js b/esbuild.config.js
index ce5b824..8f9c355 100644
--- a/esbuild.config.js
+++ b/esbuild.config.js
@@ -3,30 +3,30 @@
const process = require("process");
const builtins = require("builtin-modules");
-const prod = (process.argv[2] === "production");
-
+const prod = process.argv[2] === "production";
module.exports = {
- platform: "browser",
- bundle: true,
- external: [
- "obsidian",
- "electron",
- "@codemirror/autocomplete",
- "@codemirror/collab",
- "@codemirror/commands",
- "@codemirror/language",
- "@codemirror/lint",
- "@codemirror/search",
- "@codemirror/state",
- "@codemirror/view",
- "@lezer/common",
- "@lezer/highlight",
- "@lezer/lr",
- ...builtins],
- format: "cjs",
- target: "es2018",
- sourcemap: prod ? false : "inline",
- treeShaking: true,
- loader: { '.ttf': 'dataurl' },
-}
\ No newline at end of file
+ platform: "browser",
+ bundle: true,
+ external: [
+ "obsidian",
+ "electron",
+ "@codemirror/autocomplete",
+ "@codemirror/collab",
+ "@codemirror/commands",
+ "@codemirror/language",
+ "@codemirror/lint",
+ "@codemirror/search",
+ "@codemirror/state",
+ "@codemirror/view",
+ "@lezer/common",
+ "@lezer/highlight",
+ "@lezer/lr",
+ ...builtins,
+ ],
+ format: "cjs",
+ target: "es2018",
+ sourcemap: prod ? false : "inline",
+ treeShaking: true,
+ loader: { ".ttf": "dataurl" },
+};
diff --git a/package-lock.json b/package-lock.json
index 3b0a1b1..7c9576f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,8 +9,8 @@
"version": "1.16.0",
"license": "MIT",
"dependencies": {
- "@react-pdf/renderer": "^4.3.2",
"moment": "^2.30.1",
+ "pdfmake": "^0.3.7",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"uuid": "^11.0.5",
@@ -19,6 +19,7 @@
"devDependencies": {
"@types/jest": "^30.0.0",
"@types/node": "^16.11.6",
+ "@types/pdfmake": "^0.3.2",
"@types/react": "^19.2.4",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "8.57.2",
@@ -496,15 +497,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/runtime": {
- "version": "7.29.2",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
- "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
@@ -1728,6 +1720,30 @@
"@tybys/wasm-util": "^0.10.0"
}
},
+ "node_modules/@noble/ciphers": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
+ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -1752,174 +1768,6 @@
"url": "https://opencollective.com/pkgr"
}
},
- "node_modules/@react-pdf/fns": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.2.tgz",
- "integrity": "sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==",
- "license": "MIT"
- },
- "node_modules/@react-pdf/font": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.4.tgz",
- "integrity": "sha512-8YtgGtL511txIEc9AjiilpZ7yjid8uCd8OGUl6jaL3LIHnrToUupSN4IzsMQpVTCMYiDLFnDNQzpZsOYtRS/Pg==",
- "license": "MIT",
- "dependencies": {
- "@react-pdf/pdfkit": "^4.1.0",
- "@react-pdf/types": "^2.9.2",
- "fontkit": "^2.0.2",
- "is-url": "^1.2.4"
- }
- },
- "node_modules/@react-pdf/image": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@react-pdf/image/-/image-3.0.4.tgz",
- "integrity": "sha512-z0ogVQE0bKqgXQ5smgzIU857rLV7bMgVdrYsu3UfXDDLSzI7QPvzf6MFTFllX6Dx2rcsF13E01dqKPtJEM799g==",
- "license": "MIT",
- "dependencies": {
- "@react-pdf/png-js": "^3.0.0",
- "jay-peg": "^1.1.1"
- }
- },
- "node_modules/@react-pdf/layout": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.4.2.tgz",
- "integrity": "sha512-gNu2oh8MiGR+NJZYTJ4c4q0nWCESBI6rKFiodVhE7OeVAjtzZzd6l65wsN7HXdWJqOZD3ttD97iE+tf5SOd/Yg==",
- "license": "MIT",
- "dependencies": {
- "@react-pdf/fns": "3.1.2",
- "@react-pdf/image": "^3.0.4",
- "@react-pdf/primitives": "^4.1.1",
- "@react-pdf/stylesheet": "^6.1.2",
- "@react-pdf/textkit": "^6.1.0",
- "@react-pdf/types": "^2.9.2",
- "emoji-regex-xs": "^1.0.0",
- "queue": "^6.0.1",
- "yoga-layout": "^3.2.1"
- }
- },
- "node_modules/@react-pdf/pdfkit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-4.1.0.tgz",
- "integrity": "sha512-Wm/IOAv0h/U5Ra94c/PltFJGcpTUd/fwVMVeFD6X9tTTPCttIwg0teRG1Lqq617J8K4W7jpL/B0HTH0mjp3QpQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.20.13",
- "@react-pdf/png-js": "^3.0.0",
- "browserify-zlib": "^0.2.0",
- "crypto-js": "^4.2.0",
- "fontkit": "^2.0.2",
- "jay-peg": "^1.1.1",
- "linebreak": "^1.1.0",
- "vite-compatible-readable-stream": "^3.6.1"
- }
- },
- "node_modules/@react-pdf/png-js": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@react-pdf/png-js/-/png-js-3.0.0.tgz",
- "integrity": "sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==",
- "license": "MIT",
- "dependencies": {
- "browserify-zlib": "^0.2.0"
- }
- },
- "node_modules/@react-pdf/primitives": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.1.1.tgz",
- "integrity": "sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ==",
- "license": "MIT"
- },
- "node_modules/@react-pdf/reconciler": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@react-pdf/reconciler/-/reconciler-2.0.0.tgz",
- "integrity": "sha512-7zaPRujpbHSmCpIrZ+b9HSTJHthcVZzX0Wx7RzvQGsGBUbHP4p6s5itXrAIOuQuPvDepoHGNOvf6xUuMVvdoyw==",
- "license": "MIT",
- "dependencies": {
- "object-assign": "^4.1.1",
- "scheduler": "0.25.0-rc-603e6108-20241029"
- },
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/@react-pdf/render": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.3.2.tgz",
- "integrity": "sha512-el5KYM1sH/PKcO4tRCIm8/AIEmhtraaONbwCrBhFdehoGv6JtgnXiMxHGAvZbI5kEg051GbyP+XIU6f6YbOu6Q==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.20.13",
- "@react-pdf/fns": "3.1.2",
- "@react-pdf/primitives": "^4.1.1",
- "@react-pdf/textkit": "^6.1.0",
- "@react-pdf/types": "^2.9.2",
- "abs-svg-path": "^0.1.1",
- "color-string": "^1.9.1",
- "normalize-svg-path": "^1.1.0",
- "parse-svg-path": "^0.1.2",
- "svg-arc-to-cubic-bezier": "^3.2.0"
- }
- },
- "node_modules/@react-pdf/renderer": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.3.2.tgz",
- "integrity": "sha512-EhPkj35gO9rXIyyx29W3j3axemvVY5RigMmlK4/6Ku0pXB8z9PEE/sz4ZBOShu2uot6V4xiCR3aG+t9IjJJlBQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.20.13",
- "@react-pdf/fns": "3.1.2",
- "@react-pdf/font": "^4.0.4",
- "@react-pdf/layout": "^4.4.2",
- "@react-pdf/pdfkit": "^4.1.0",
- "@react-pdf/primitives": "^4.1.1",
- "@react-pdf/reconciler": "^2.0.0",
- "@react-pdf/render": "^4.3.2",
- "@react-pdf/types": "^2.9.2",
- "events": "^3.3.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "queue": "^6.0.1"
- },
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/@react-pdf/stylesheet": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.2.tgz",
- "integrity": "sha512-E3ftGRYUQGKiN3JOgtGsLDo0hGekA6dmkmi/MYACytmPTKxQRBSO3126MebmCq+t1rgU9uRlREIEawJ+8nzSbw==",
- "license": "MIT",
- "dependencies": {
- "@react-pdf/fns": "3.1.2",
- "@react-pdf/types": "^2.9.2",
- "color-string": "^1.9.1",
- "hsl-to-hex": "^1.0.0",
- "media-engine": "^1.0.3",
- "postcss-value-parser": "^4.1.0"
- }
- },
- "node_modules/@react-pdf/textkit": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.1.0.tgz",
- "integrity": "sha512-sFlzDC9CDFrJsnL3B/+NHrk9+Advqk7iJZIStiYQDdskbow8GF/AGYrpIk+vWSnh35YxaGbHkqXq53XOxnyrjQ==",
- "license": "MIT",
- "dependencies": {
- "@react-pdf/fns": "3.1.2",
- "bidi-js": "^1.0.2",
- "hyphen": "^1.6.4",
- "unicode-properties": "^1.4.1"
- }
- },
- "node_modules/@react-pdf/types": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.9.2.tgz",
- "integrity": "sha512-dufvpKId9OajLLbgn9q7VLUmyo1Jf+iyGk2ZHmCL8nIDtL8N1Ejh9TH7+pXXrR0tdie1nmnEb5Bz9U7g4hI4/g==",
- "license": "MIT",
- "dependencies": {
- "@react-pdf/font": "^4.0.4",
- "@react-pdf/primitives": "^4.1.1",
- "@react-pdf/stylesheet": "^6.1.2"
- }
- },
"node_modules/@sinclair/typebox": {
"version": "0.34.48",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz",
@@ -2090,6 +1938,27 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/pdfkit": {
+ "version": "0.17.5",
+ "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.17.5.tgz",
+ "integrity": "sha512-T3ZHnvF91HsEco5ClhBCOuBwobZfPcI2jaiSHybkkKYq4KhVIIurod94JVKvDIG0JXT6o3KiERC0X0//m8dyrg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/pdfmake": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@types/pdfmake/-/pdfmake-0.3.2.tgz",
+ "integrity": "sha512-2TZSL8puKJs/rHvMV1b8BhHD+qYyV9da8mVY83/x7ZR/NaEPXbm3+t5SwkwaH6QAIhY1zQVAaFDhHWL0haMstA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/pdfkit": "*"
+ }
+ },
"node_modules/@types/react": {
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
@@ -2653,12 +2522,6 @@
"win32"
]
},
- "node_modules/abs-svg-path": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz",
- "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==",
- "license": "MIT"
- },
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
@@ -2926,15 +2789,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/bidi-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
- "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
- "license": "MIT",
- "dependencies": {
- "require-from-string": "^2.0.2"
- }
- },
"node_modules/brace-expansion": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
@@ -2957,15 +2811,6 @@
"base64-js": "^1.1.2"
}
},
- "node_modules/browserify-zlib": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
- "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
- "license": "MIT",
- "dependencies": {
- "pako": "~1.0.5"
- }
- },
"node_modules/browserslist": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
@@ -3272,18 +3117,9 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/color-string": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
- "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
- "license": "MIT",
- "dependencies": {
- "color-name": "^1.0.0",
- "simple-swizzle": "^0.2.2"
- }
- },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -3321,12 +3157,6 @@
"node": ">= 8"
}
},
- "node_modules/crypto-js": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
- "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
- "license": "MIT"
- },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -3435,12 +3265,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/emoji-regex-xs": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz",
- "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==",
- "license": "MIT"
- },
"node_modules/error-ex": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
@@ -3762,15 +3586,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/events": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
- "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
- "license": "MIT",
- "engines": {
- "node": ">=0.8.x"
- }
- },
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@@ -4141,21 +3956,6 @@
"node": ">=8"
}
},
- "node_modules/hsl-to-hex": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz",
- "integrity": "sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==",
- "license": "MIT",
- "dependencies": {
- "hsl-to-rgb-for-reals": "^1.1.0"
- }
- },
- "node_modules/hsl-to-rgb-for-reals": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz",
- "integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==",
- "license": "ISC"
- },
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -4173,12 +3973,6 @@
"node": ">=10.17.0"
}
},
- "node_modules/hyphen": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.14.1.tgz",
- "integrity": "sha512-kvL8xYl5QMTh+LwohVN72ciOxC0OEV79IPdJSTwEXok9y9QHebXGdFgrED4sWfiax/ODx++CAMk3hMy4XPJPOw==",
- "license": "ISC"
- },
"node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
@@ -4235,6 +4029,7 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
"license": "ISC"
},
"node_modules/is-arrayish": {
@@ -4302,12 +4097,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-url": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
- "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
- "license": "MIT"
- },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -4402,15 +4191,6 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
- "node_modules/jay-peg": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/jay-peg/-/jay-peg-1.1.1.tgz",
- "integrity": "sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==",
- "license": "MIT",
- "dependencies": {
- "restructure": "^3.0.0"
- }
- },
"node_modules/jest": {
"version": "30.3.0",
"resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz",
@@ -4990,10 +4770,17 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/js-md5": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz",
+ "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==",
+ "license": "MIT"
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
@@ -5153,18 +4940,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -5208,12 +4983,6 @@
"tmpl": "1.0.5"
}
},
- "node_modules/media-engine": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz",
- "integrity": "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==",
- "license": "MIT"
- },
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -5347,15 +5116,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/normalize-svg-path": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz",
- "integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==",
- "license": "MIT",
- "dependencies": {
- "svg-arc-to-cubic-bezier": "^3.0.0"
- }
- },
"node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@@ -5369,15 +5129,6 @@
"node": ">=8"
}
},
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/obsidian": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz",
@@ -5498,12 +5249,6 @@
"dev": true,
"license": "BlueOak-1.0.0"
},
- "node_modules/pako": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
- "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
- "license": "(MIT AND Zlib)"
- },
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@@ -5523,12 +5268,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/parse-svg-path": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz",
- "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==",
- "license": "MIT"
- },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -5583,6 +5322,34 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/pdfkit": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.18.0.tgz",
+ "integrity": "sha512-NvUwSDZ0eYEzqAiWwVQkRkjYUkZ48kcsHuCO31ykqPPIVkwoSDjDGiwIgHHNtsiwls3z3P/zy4q00hl2chg2Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/ciphers": "^1.0.0",
+ "@noble/hashes": "^1.6.0",
+ "fontkit": "^2.0.4",
+ "js-md5": "^0.8.3",
+ "linebreak": "^1.1.0",
+ "png-js": "^1.0.0"
+ }
+ },
+ "node_modules/pdfmake": {
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.3.7.tgz",
+ "integrity": "sha512-SwTFcaH3kCJBlPFWi/YB34zRg6lpCxq90tkZ9GxfSi9/v4Tk96cv4IvOstA+CC40rdW1OzQIuNhD2DLD1RDVgA==",
+ "license": "MIT",
+ "dependencies": {
+ "linebreak": "^1.1.0",
+ "pdfkit": "^0.18.0",
+ "xmldoc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -5682,11 +5449,10 @@
"node": ">=8"
}
},
- "node_modules/postcss-value-parser": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "license": "MIT"
+ "node_modules/png-js": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz",
+ "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g=="
},
"node_modules/prelude-ls": {
"version": "1.2.1",
@@ -5743,23 +5509,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
- "node_modules/prop-types/node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "license": "MIT"
- },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -5788,15 +5537,6 @@
],
"license": "MIT"
},
- "node_modules/queue": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
- "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
- "license": "MIT",
- "dependencies": {
- "inherits": "~2.0.3"
- }
- },
"node_modules/react": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
@@ -5855,15 +5595,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/require-from-string": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/resolve-cwd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
@@ -5893,31 +5624,14 @@
"integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==",
"license": "MIT"
},
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/scheduler": {
- "version": "0.25.0-rc-603e6108-20241029",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz",
- "integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==",
- "license": "MIT"
+ "node_modules/sax": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
+ "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
},
"node_modules/semver": {
"version": "7.7.4",
@@ -5968,21 +5682,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/simple-swizzle": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
- "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
- "license": "MIT",
- "dependencies": {
- "is-arrayish": "^0.3.1"
- }
- },
- "node_modules/simple-swizzle/node_modules/is-arrayish": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
- "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
- "license": "MIT"
- },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -6044,15 +5743,6 @@
"node": ">=8"
}
},
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
"node_modules/string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
@@ -6248,12 +5938,6 @@
"node": ">=8"
}
},
- "node_modules/svg-arc-to-cubic-bezier": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz",
- "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==",
- "license": "ISC"
- },
"node_modules/synckit": {
"version": "0.11.12",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
@@ -6621,12 +6305,6 @@
"punycode": "^2.1.0"
}
},
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "license": "MIT"
- },
"node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
@@ -6655,20 +6333,6 @@
"node": ">=10.12.0"
}
},
- "node_modules/vite-compatible-readable-stream": {
- "version": "3.6.1",
- "resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz",
- "integrity": "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==",
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
@@ -6837,6 +6501,18 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
+ "node_modules/xmldoc": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-2.0.3.tgz",
+ "integrity": "sha512-6gRk4NY/Jvg67xn7OzJuxLRsGgiXBaPUQplVJ/9l99uIugxh4FTOewYz5ic8WScj7Xx/2WvhENiQKwkK9RpE4w==",
+ "license": "MIT",
+ "dependencies": {
+ "sax": "^1.4.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -6941,12 +6617,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/yoga-layout": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
- "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
- "license": "MIT"
- },
"node_modules/zod": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
diff --git a/package.json b/package.json
index ebdc85a..81ef6c3 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"devDependencies": {
"@types/jest": "^30.0.0",
"@types/node": "^16.11.6",
+ "@types/pdfmake": "^0.3.2",
"@types/react": "^19.2.4",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "8.57.2",
@@ -33,8 +34,8 @@
"typescript": "5.9.2"
},
"dependencies": {
- "@react-pdf/renderer": "^4.3.2",
"moment": "^2.30.1",
+ "pdfmake": "^0.3.7",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"uuid": "^11.0.5",
diff --git a/src/components/pdf/TimesheetPdf.tsx b/src/components/pdf/TimesheetPdf.tsx
deleted file mode 100644
index daf2d93..0000000
--- a/src/components/pdf/TimesheetPdf.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import React from "react";
-import type { Moment } from "moment";
-import { Timekeep } from "@/timekeep/schema";
-import { getTotalDuration } from "@/timekeep";
-import { TimekeepSettings } from "@/settings";
-import { Page, View, Text, Document } from "@react-pdf/renderer";
-import {
- formatPdfDate,
- formatDurationLong,
- formatDurationShort,
-} from "@/utils";
-
-import { createStyles } from "./styles";
-import TimesheetPdfTable from "./TimesheetPdfTable";
-import TimesheetPdfDetailField from "./TimesheetPdfDetailField";
-
-type Props = {
- title: string;
- data: Timekeep;
- currentTime: Moment;
- footnote: string;
- settings: TimekeepSettings;
-};
-
-export default function TimesheetPdf({
- title,
- data,
- currentTime,
- footnote,
- settings,
-}: Props) {
- // Get the total elapsed duration
- const duration = getTotalDuration(data.entries, currentTime);
-
- const currentDate = formatPdfDate(currentTime, settings);
- const totalDuration = formatDurationLong(duration);
- const totalDurationShort = formatDurationShort(duration);
-
- const styles = createStyles(settings.pdfFontFamily);
-
- return (
-
-
-
- {title}
- Timesheet
-
-
-
-
-
-
-
-
-
-
-
- {footnote}
-
-
-
- );
-}
diff --git a/src/components/pdf/TimesheetPdfDetailField.tsx b/src/components/pdf/TimesheetPdfDetailField.tsx
deleted file mode 100644
index 140df4d..0000000
--- a/src/components/pdf/TimesheetPdfDetailField.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from "react";
-import { Text } from "@react-pdf/renderer";
-
-import { Styles } from "./styles";
-
-type Props = {
- name: string;
- value: string;
- styles: Styles;
-};
-
-// Individual field within the details section
-export default function TimesheetPdfDetailField({
- name,
- value,
- styles,
-}: Props) {
- return (
-
- {name}:
- {value}
-
- );
-}
diff --git a/src/components/pdf/TimesheetPdfTable.tsx b/src/components/pdf/TimesheetPdfTable.tsx
deleted file mode 100644
index 478e4b6..0000000
--- a/src/components/pdf/TimesheetPdfTable.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import React from "react";
-import type { Moment } from "moment";
-import { Timekeep } from "@/timekeep/schema";
-import { TimekeepSettings } from "@/settings";
-import { View, Text } from "@react-pdf/renderer";
-
-import { Styles } from "./styles";
-import { TimesheetPdfTableRow } from "./TimesheetPdfTableRow";
-
-type Props = {
- data: Timekeep;
- currentTime: Moment;
- totalDuration: string;
- settings: TimekeepSettings;
- styles: Styles;
-};
-
-export default function TimesheetPdfTable({
- data,
- currentTime,
- totalDuration,
- settings,
- styles,
-}: Props) {
- // Render the table header
- const renderHeader = (
-
-
- Block Name
-
-
- Start Time
-
-
- End Time
-
-
- Duration
-
-
- );
-
- // Render the rows of the table
- const renderEntries = data.entries.map((entry, index) => (
-
- ));
-
- // Render the table footer
- const renderFooter = (
-
- Total
-
-
-
-
-
- {totalDuration}
-
-
- );
-
- return (
-
- {renderHeader}
- {renderEntries}
- {renderFooter}
-
- );
-}
diff --git a/src/components/pdf/TimesheetPdfTableRow.tsx b/src/components/pdf/TimesheetPdfTableRow.tsx
deleted file mode 100644
index 48c172d..0000000
--- a/src/components/pdf/TimesheetPdfTableRow.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import type { Moment } from "moment";
-import React, { Fragment } from "react";
-import { getEntryDuration } from "@/timekeep";
-import { TimekeepSettings } from "@/settings";
-import { TimeEntry } from "@/timekeep/schema";
-import { View, Text } from "@react-pdf/renderer";
-import { formatPdfRowDate, formatDurationLong } from "@/utils";
-
-import { Styles } from "./styles";
-
-type Props = {
- entry: TimeEntry;
- currentTime: Moment;
- settings: TimekeepSettings;
- styles: Styles;
-};
-
-export function TimesheetPdfTableRow({
- entry,
- currentTime,
- settings,
- styles,
-}: Props) {
- const duration = getEntryDuration(entry, currentTime);
- const durationFormatted = formatDurationLong(duration);
-
- // Render start and end timing for individual entries
- const renderTiming = entry.startTime !== null && (
- <>
-
- {formatPdfRowDate(entry.startTime, settings)}
-
-
- {formatPdfRowDate(entry.endTime ?? currentTime, settings)}
-
- >
- );
-
- // Render the child rows for groups
- const renderChildren = entry.subEntries != null && (
-
- {entry.subEntries.map((entry, index) => (
-
- ))}
-
- );
-
- return (
-
-
-
- {entry.name}
-
- {renderTiming}
-
- {durationFormatted}
-
-
-
- {renderChildren}
-
- );
-}
diff --git a/src/components/pdf/index.ts b/src/components/pdf/index.ts
index c7a6725..2832ca2 100644
--- a/src/components/pdf/index.ts
+++ b/src/components/pdf/index.ts
@@ -1,45 +1,7 @@
-import React from "react";
import { Moment } from "moment";
import { Timekeep } from "@/timekeep/schema";
import { TimekeepSettings } from "@/settings";
-import RubikBold from "@/fonts/Rubik-Bold.ttf";
-import { pdf, Font } from "@react-pdf/renderer";
-// Embedded fonts
-import RobotoBold from "@/fonts/Roboto-Bold.ttf";
-import RubikRegular from "@/fonts/Rubik-Regular.ttf";
-import RobotoRegular from "@/fonts/Roboto-Regular.ttf";
-
-import TimesheetPdf from "./TimesheetPdf";
-
-// Register the embedded font (Roboto for a large range of supported languages/characters https://github.com/jacobtread/obsidian-timekeep/issues/1)
-Font.register({
- family: "Roboto",
- fonts: [
- {
- src: RobotoRegular,
- fontWeight: 400,
- },
- {
- src: RobotoBold,
- fontWeight: 700,
- },
- ],
-});
-
-// Register the Rubik font for Arabic character support (https://github.com/jacobtread/obsidian-timekeep/issues/61)
-Font.register({
- family: "Rubik",
- fonts: [
- {
- src: RubikRegular,
- fontWeight: 400,
- },
- {
- src: RubikBold,
- fontWeight: 700,
- },
- ],
-});
+import { createPdfExport } from "@/components/pdf/renderer";
/**
* Generates a PDF for the provided timekeep
@@ -54,18 +16,5 @@ export async function createPdf(
settings: TimekeepSettings,
currentTime: Moment
): Promise {
- const document = React.createElement(TimesheetPdf, {
- data: timekeep,
- title: settings.pdfTitle,
- footnote: settings.pdfFootnote,
- currentTime,
- settings,
- });
-
- // Create the PDF
- const createdPdf = pdf(document);
-
- // Create a blob from the PDF
- const buffer = await createdPdf.toBuffer();
- return buffer;
+ return createPdfExport(timekeep, settings, currentTime);
}
diff --git a/src/components/pdf/renderer.ts b/src/components/pdf/renderer.ts
new file mode 100644
index 0000000..f97230a
--- /dev/null
+++ b/src/components/pdf/renderer.ts
@@ -0,0 +1,378 @@
+import type { TimeEntry, Timekeep } from "@/timekeep/schema";
+import type { TimekeepSettings } from "@/settings";
+import type { Moment } from "moment";
+
+import pdfMake from "pdfmake";
+import type {
+ DynamicContent,
+ Table,
+ TableCell,
+ TDocumentDefinitions,
+ TFontDictionary,
+} from "pdfmake/interfaces";
+
+// Embedded fonts
+import RobotoBold from "@/fonts/Roboto-Bold.ttf";
+import RubikRegular from "@/fonts/Rubik-Regular.ttf";
+import RubikBold from "@/fonts/Rubik-Bold.ttf";
+import RobotoRegular from "@/fonts/Roboto-Regular.ttf";
+import { getEntryDuration, getTotalDuration } from "@/timekeep";
+import {
+ formatDurationLong,
+ formatDurationShort,
+ formatPdfDate,
+ formatPdfRowDate,
+} from "@/utils";
+import { Readable } from "stream";
+
+const fonts: TFontDictionary = {
+ Roboto: {
+ normal: "Roboto-Regular.ttf",
+ bold: "Roboto-Bold.ttf",
+ },
+ Rubik: {
+ normal: "Rubik-Regular.ttf",
+ bold: "Rubik-Bold.ttf",
+ },
+};
+
+function stripDataUrlPrefix(text: string): string {
+ return text.substring("data:font/ttf;base64,".length);
+}
+
+pdfMake.addVirtualFileSystem({
+ "Roboto-Regular.ttf": stripDataUrlPrefix(RobotoRegular),
+ "Roboto-Bold.ttf": stripDataUrlPrefix(RobotoBold),
+ "Rubik-Regular.ttf": stripDataUrlPrefix(RubikRegular),
+ "Rubik-Bold.ttf": stripDataUrlPrefix(RubikBold),
+});
+
+pdfMake.addFonts(fonts);
+
+export async function createPdfExport(
+ timekeep: Timekeep,
+ settings: TimekeepSettings,
+ currentTime: Moment
+): Promise {
+ const definition = createPdfDefinition(timekeep, settings, currentTime);
+ const pdf = pdfMake.createPdf(definition, {});
+ const stream = await pdf.getBuffer();
+ return Readable.from([stream]);
+}
+
+export function createPdfDefinition(
+ timekeep: Timekeep,
+ settings: TimekeepSettings,
+ currentTime: Moment
+): TDocumentDefinitions {
+ const duration = getTotalDuration(timekeep.entries, currentTime);
+ const currentDate = formatPdfDate(currentTime, settings);
+ const totalDuration = formatDurationLong(duration);
+ const totalDurationShort = formatDurationShort(duration);
+
+ return {
+ content: [
+ createPdfHeader(
+ settings.pdfTitle,
+ currentDate,
+ totalDuration,
+ totalDurationShort
+ ),
+ createPdfTable(timekeep, totalDuration, settings, currentTime),
+ ],
+ footer: createPdfFooter(settings),
+ defaultStyle: {
+ font: settings.pdfFontFamily,
+ },
+ pageSize: "A4",
+ pageMargins: 15,
+ styles: {
+ heading: {
+ marginBottom: 5,
+ columnGap: 10,
+ },
+ title: {
+ bold: true,
+ fontSize: 12,
+ marginBottom: 5,
+ },
+ details: {
+ marginBottom: 5,
+ },
+ footNote: {
+ noWrap: true,
+ fontSize: 6,
+ bold: true,
+ },
+ pageNumber: {
+ noWrap: true,
+ fontSize: 6,
+ },
+ footer: {
+ marginLeft: 15,
+ marginRight: 15,
+ },
+ tableCell: {
+ fontSize: 8,
+ },
+ tableCellTime: {
+ alignment: "right",
+ },
+ tableCellHeader: {
+ bold: true,
+ },
+ },
+ };
+}
+
+function createPdfHeader(
+ pdfTitle: string,
+ currentDate: string,
+ totalDuration: string,
+ totalDurationShort: string
+): pdfMake.Content {
+ return [
+ {
+ columns: [
+ {
+ text: pdfTitle,
+ alignment: "left",
+ style: "title",
+ width: "50%",
+ },
+ {
+ text: "Timesheet",
+ alignment: "right",
+ style: "title",
+ width: "50%",
+ },
+ ],
+ style: "heading",
+ },
+ {
+ stack: [
+ createPdfHeaderDetailField("Date", currentDate),
+ createPdfHeaderDetailField("Total Duration", totalDuration),
+ createPdfHeaderDetailField(
+ "Total Duration (hours)",
+ totalDurationShort
+ ),
+ ],
+
+ style: "details",
+ },
+ ];
+}
+
+function createPdfHeaderDetailField(
+ title: string,
+ value: string
+): pdfMake.Content {
+ return {
+ text: [
+ { text: title + ": ", fontSize: 8, bold: true },
+ { text: value, fontSize: 8 },
+ ],
+ marginBottom: 8,
+ };
+}
+
+function createPdfFooter(settings: TimekeepSettings): DynamicContent {
+ return function (currentPage: number, pageCount: number) {
+ return {
+ columns: [
+ {
+ text: `${settings.pdfFootnote}`,
+ style: "footNote",
+ width: "50%",
+ alignment: "left",
+ },
+ {
+ text: `${currentPage} of ${pageCount}`,
+ style: "pageNumber",
+ width: "50%",
+ alignment: "right",
+ },
+ ],
+ style: "footer",
+ };
+ };
+}
+
+function createPdfTable(
+ timekeep: Timekeep,
+ totalDuration: string,
+ settings: TimekeepSettings,
+ currentTime: Moment
+): pdfMake.Content {
+ const rows = createPdfTableRows(timekeep.entries, settings, currentTime);
+
+ return {
+ table: {
+ headerRows: 1,
+ widths: ["*", 70, 70, 50],
+ body: [
+ [
+ {
+ text: "Block Name",
+ style: ["tableCell", "tableCellHeader"],
+ border: [true, false, false, true],
+ },
+ {
+ text: "Start Time",
+ style: ["tableCell", "tableCellHeader"],
+ alignment: "center",
+ border: [false, false, false, true],
+ },
+ {
+ text: "End Time",
+ style: ["tableCell", "tableCellHeader"],
+ alignment: "center",
+ border: [false, false, false, true],
+ },
+ {
+ text: "Duration",
+ style: ["tableCell", "tableCellHeader"],
+ alignment: "right",
+ border: [false, false, true, true],
+ },
+ ],
+ ...rows.map((row) => row.row),
+ [
+ { text: "Total", style: "tableCell" },
+ { text: "", style: "tableCell" },
+ { text: "", style: "tableCell" },
+ {
+ text: totalDuration,
+ style: ["tableCell", "tableCellFooter"],
+ bold: true,
+ alignment: "right",
+ },
+ ],
+ ],
+ },
+
+ layout: {
+ fillColor: function (rowIndex, node, columnIndex) {
+ // Header & Footer styling
+ if (rowIndex === 0 || rowIndex === rows.length + 1) {
+ return "#ececec";
+ }
+
+ // Group row styling
+ if (rowIndex > 0 && rowIndex - 1 < rows.length) {
+ if (rows[rowIndex - 1].group) {
+ return "#f4f4f4";
+ }
+ }
+
+ // Default styling
+ return "#ffffff";
+ },
+
+ vLineColor: function (columnIndex, node, rowIndex) {
+ return "#ececec";
+ },
+
+ hLineColor: function (rowIndex, node, columnIndex) {
+ return "#ececec";
+ },
+ paddingBottom: cellPadding,
+ paddingLeft: cellPadding,
+ paddingTop: cellPadding,
+ paddingRight: cellPadding,
+ },
+ };
+}
+
+const cellPadding = () => 8;
+
+type TableEntryRow = {
+ row: TableCell[];
+ depth: number;
+ group: boolean;
+};
+
+function createPdfTableRows(
+ entries: TimeEntry[],
+ settings: TimekeepSettings,
+ currentTime: Moment
+): TableEntryRow[] {
+ type StackEntry = { entry: TimeEntry; depth: number };
+
+ const rows: TableEntryRow[] = [];
+ const stack: StackEntry[] = entries.map((entry) => ({ entry, depth: 0 }));
+
+ while (stack.length > 0) {
+ const { entry, depth } = stack.pop()!;
+
+ rows.push({
+ group: entry.subEntries !== null && entry.subEntries.length > 0,
+ row: createTableEntryCells(entry, depth, settings, currentTime),
+ depth,
+ });
+
+ if (entry.subEntries && entry.subEntries.length > 0) {
+ for (let i = entry.subEntries.length - 1; i >= 0; i--) {
+ stack.push({
+ entry: entry.subEntries[i],
+ depth: depth + 1,
+ });
+ }
+ }
+ }
+
+ return rows;
+}
+
+function createTableEntryCells(
+ entry: TimeEntry,
+ depth: number,
+ settings: TimekeepSettings,
+ currentTime: Moment
+): TableCell[] {
+ const duration = getEntryDuration(entry, currentTime);
+ const durationFormatted = formatDurationLong(duration);
+
+ let startTime: string;
+ let endTime: string;
+
+ if (entry.startTime !== null) {
+ startTime = formatPdfRowDate(entry.startTime, settings);
+ endTime = formatPdfRowDate(entry.endTime ?? currentTime, settings);
+ } else {
+ startTime = "";
+ endTime = "";
+ }
+
+ const marginLeft = depth * 5;
+
+ if (depth > 0) {
+ const bg = "#999";
+ }
+
+ return [
+ {
+ text: entry.name,
+ style: ["tableCell", "tableCellBlock"],
+ marginLeft,
+ border: [true, false, false, true],
+ },
+ {
+ text: startTime,
+ style: ["tableCell", "tableCellTime"],
+ alignment: "center",
+ },
+ {
+ text: endTime,
+ style: ["tableCell", "tableCellTime"],
+ alignment: "center",
+ },
+ {
+ text: durationFormatted,
+ style: ["tableCell", "tableCellTime"],
+ alignment: "right",
+ border: [false, false, true, true],
+ },
+ ];
+}
diff --git a/src/components/pdf/styles.ts b/src/components/pdf/styles.ts
deleted file mode 100644
index fc29483..0000000
--- a/src/components/pdf/styles.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { StyleSheet } from "@react-pdf/renderer";
-
-export type Styles = ReturnType;
-
-export function createStyles(fontFamily = "Roboto") {
- return StyleSheet.create({
- // Styles for a page
- page: {
- fontFamily,
- padding: 15,
- },
-
- // Heading with the page title and timesheet title
- heading: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- marginBottom: 5,
- },
-
- // User specified title and "Timesheet"
- title: {
- fontFamily,
- fontWeight: 700,
- fontSize: 12,
- marginBottom: 5,
- },
-
- // Detail items at the top (Date, Total Duration, Short Duration)
- details: {
- gap: 4,
- marginBottom: 5,
- },
-
- // Footer note
- footNote: {
- marginTop: 10,
- fontSize: 6,
- fontFamily,
- fontWeight: 700,
- },
-
- // Name of a details field
- detailsFieldName: {
- fontFamily,
- fontWeight: 700,
- fontSize: 8,
- marginBottom: 5,
- },
-
- // Value of a details field
- detailsFieldValue: {
- fontSize: 8,
- marginBottom: 5,
- },
-
- // Container around the timesheet table
- tableContainer: {
- flexDirection: "row",
- flexWrap: "wrap",
- borderWidth: 1,
- borderColor: "#ececec",
- },
-
- // Row within the table
- tableRow: {
- flexDirection: "row",
- borderBottomColor: "#ececec",
- borderBottomWidth: 1,
- alignItems: "center",
- height: 24,
- fontSize: 8,
- width: "100%",
- },
-
- // Header row
- tableRowHeader: {
- fontFamily,
- fontWeight: 700,
- backgroundColor: "#ececec",
- },
-
- // Base table cell
- tableCell: {
- padding: 15,
- },
-
- // Table cell that spans available width
- tableCellBlock: {
- width: "100%",
- },
-
- // Table cell that contains a timestamp
- tableCellTime: {
- width: "200px",
- textAlign: "right",
- },
-
- // Table cell that contains a duration
- tableCellDuration: {
- width: "400px",
- textAlign: "right",
- fontWeight: 700,
- fontFamily,
- },
-
- // Wrapper for nested rows
- tableRowWrapper: {
- borderColor: "#f9f9f9",
- borderWidth: 5,
- },
-
- // Table indentation container
- tableIndent: {
- borderLeftWidth: 5,
- borderLeftColor: "#999",
- },
- });
-}
diff --git a/src/timekeep/create.test.ts b/src/timekeep/create.test.ts
index acb434b..2fcc71c 100644
--- a/src/timekeep/create.test.ts
+++ b/src/timekeep/create.test.ts
@@ -27,9 +27,8 @@ describe("createEntry", () => {
describe("withEntry", () => {
it("should add new entry to entries", async () => {
- const { input, currentTime, expected } = await import(
- "./__fixtures__/manipulating/adding_entry/addNewEntry"
- );
+ const { input, currentTime, expected } =
+ await import("./__fixtures__/manipulating/adding_entry/addNewEntry");
const output = withEntry(input, "New Entry", currentTime);
expect(stripEntriesRuntimeData(output)).toEqual(
stripEntriesRuntimeData(expected)
@@ -37,9 +36,8 @@ describe("withEntry", () => {
});
it("should generate block name when empty", async () => {
- const { input, currentTime, expected } = await import(
- "./__fixtures__/manipulating/adding_entry/addEmptyBlockName"
- );
+ const { input, currentTime, expected } =
+ await import("./__fixtures__/manipulating/adding_entry/addEmptyBlockName");
const output1 = withEntry(input, "", currentTime);
expect(stripEntriesRuntimeData(output1)).toEqual(
@@ -54,9 +52,8 @@ describe("withEntry", () => {
});
it("should maintain existing entries when adding to a list", async () => {
- const { input, currentTime, expected } = await import(
- "./__fixtures__/manipulating/adding_entry/addEntryToList"
- );
+ const { input, currentTime, expected } =
+ await import("./__fixtures__/manipulating/adding_entry/addEntryToList");
const output = withEntry(input, "New Test Entry", currentTime);
expect(stripEntriesRuntimeData(output)).toEqual(
@@ -67,9 +64,8 @@ describe("withEntry", () => {
describe("withSubEntry", () => {
it("adding first entry should convert to group", async () => {
- const { input, currentTime, expected } = await import(
- "./__fixtures__/manipulating/adding_sub_entry/addConvertToGroup"
- );
+ const { input, currentTime, expected } =
+ await import("./__fixtures__/manipulating/adding_sub_entry/addConvertToGroup");
const output = withSubEntry(input, "New Entry", currentTime);
expect(stripEntryRuntimeData(output)).toEqual(
stripEntryRuntimeData(expected)
@@ -77,9 +73,8 @@ describe("withSubEntry", () => {
});
it("adding first entry for folder should populate subentries", async () => {
- const { input, currentTime, expected } = await import(
- "./__fixtures__/manipulating/adding_sub_entry/addFolderPopulateSubEntries"
- );
+ const { input, currentTime, expected } =
+ await import("./__fixtures__/manipulating/adding_sub_entry/addFolderPopulateSubEntries");
const output = withSubEntry(input, "New Entry", currentTime);
expect(stripEntryRuntimeData(output)).toEqual(
stripEntryRuntimeData(expected)
@@ -87,9 +82,8 @@ describe("withSubEntry", () => {
});
it("adding first entry for folder should extend subentries", async () => {
- const { input, currentTime, expected } = await import(
- "./__fixtures__/manipulating/adding_sub_entry/addFolderExtendSubEntries"
- );
+ const { input, currentTime, expected } =
+ await import("./__fixtures__/manipulating/adding_sub_entry/addFolderExtendSubEntries");
const output = withSubEntry(input, "New Entry 2", currentTime);
expect(stripEntryRuntimeData(output)).toEqual(
stripEntryRuntimeData(expected)
@@ -97,9 +91,8 @@ describe("withSubEntry", () => {
});
it("adding to group should extend sub entries", async () => {
- const { input, currentTime, expected } = await import(
- "./__fixtures__/manipulating/adding_sub_entry/addToGroupExtendSubEntries"
- );
+ const { input, currentTime, expected } =
+ await import("./__fixtures__/manipulating/adding_sub_entry/addToGroupExtendSubEntries");
const output = withSubEntry(input, "New Entry", currentTime);
expect(stripEntryRuntimeData(output)).toEqual(
@@ -108,9 +101,8 @@ describe("withSubEntry", () => {
});
it("empty name should generate a part name (single)", async () => {
- const { input, currentTime, expected } = await import(
- "./__fixtures__/manipulating/adding_sub_entry/emptyNameCreatePartNameSingle"
- );
+ const { input, currentTime, expected } =
+ await import("./__fixtures__/manipulating/adding_sub_entry/emptyNameCreatePartNameSingle");
const output = withSubEntry(input, "", currentTime);
expect(stripEntryRuntimeData(output)).toEqual(
@@ -119,9 +111,8 @@ describe("withSubEntry", () => {
});
it("empty name should generate a part name (group)", async () => {
- const { input, currentTime, expected } = await import(
- "./__fixtures__/manipulating/adding_sub_entry/emptyNameCreatePartNameGroup"
- );
+ const { input, currentTime, expected } =
+ await import("./__fixtures__/manipulating/adding_sub_entry/emptyNameCreatePartNameGroup");
const output = withSubEntry(input, "", currentTime);
expect(stripEntryRuntimeData(output)).toEqual(
diff --git a/src/timekeep/parser.test.ts b/src/timekeep/parser.test.ts
index 7ab237b..2ead34d 100644
--- a/src/timekeep/parser.test.ts
+++ b/src/timekeep/parser.test.ts
@@ -38,9 +38,8 @@ const createCodeBlock = (
describe("extracting code blocks", () => {
it("should extract codeblock contents", async () => {
- const { text, inputTimekeep1, inputTimekeep2 } = await import(
- "./__fixtures__/extracting/codeblockContents"
- );
+ const { text, inputTimekeep1, inputTimekeep2 } =
+ await import("./__fixtures__/extracting/codeblockContents");
const output = extractTimekeepCodeblocks(text);
@@ -54,9 +53,8 @@ describe("extracting code blocks", () => {
});
it("should ignore codeblocks that are not closed", async () => {
- const { text, inputTimekeep1 } = await import(
- "./__fixtures__/extracting/unclosedCodeBlock"
- );
+ const { text, inputTimekeep1 } =
+ await import("./__fixtures__/extracting/unclosedCodeBlock");
const output = extractTimekeepCodeblocks(text);
expect(stripTimekeepRuntimeData(output[0])).toStrictEqual(
diff --git a/src/timekeep/queries.test.ts b/src/timekeep/queries.test.ts
index decae6e..69f625e 100644
--- a/src/timekeep/queries.test.ts
+++ b/src/timekeep/queries.test.ts
@@ -11,27 +11,24 @@ import {
describe("getEntryById", () => {
it("find top level entry", async () => {
- const { input, targetEntry, targetEntryId } = await import(
- "./__fixtures__/checking/findEntryById"
- );
+ const { input, targetEntry, targetEntryId } =
+ await import("./__fixtures__/checking/findEntryById");
const output = getEntryById(targetEntryId, input);
expect(output).toEqual(targetEntry);
});
it("find nested entry", async () => {
- const { input, targetEntry, targetEntryId } = await import(
- "./__fixtures__/checking/findEntryByIdNested"
- );
+ const { input, targetEntry, targetEntryId } =
+ await import("./__fixtures__/checking/findEntryByIdNested");
const output = getEntryById(targetEntryId, input);
expect(output).toEqual(targetEntry);
});
it("find entry non existent", async () => {
- const { input, targetEntryId } = await import(
- "./__fixtures__/checking/findEntryByIdMissing"
- );
+ const { input, targetEntryId } =
+ await import("./__fixtures__/checking/findEntryByIdMissing");
const output = getEntryById(targetEntryId, input);
expect(output).toBeUndefined();
@@ -40,9 +37,8 @@ describe("getEntryById", () => {
describe("getPathToEntry", () => {
it("path not found", async () => {
- const { targetEntry, entries, expected } = await import(
- "./__fixtures__/path/pathNotFound"
- );
+ const { targetEntry, entries, expected } =
+ await import("./__fixtures__/path/pathNotFound");
const output = getPathToEntry(entries, targetEntry);
expect(output).toEqual(expected);
});
@@ -52,9 +48,8 @@ describe("getPathToEntry", () => {
it("deep path found", () => {});
it("should find running entry path", async () => {
- const { input, runningEntry, path } = await import(
- "./__fixtures__/checking/findRunningEntryPath"
- );
+ const { input, runningEntry, path } =
+ await import("./__fixtures__/checking/findRunningEntryPath");
const output = getPathToEntry(input, runningEntry);
expect(output).toEqual(path);
@@ -63,18 +58,16 @@ describe("getPathToEntry", () => {
describe("isEntryRunning", () => {
it("should determine entry running state", async () => {
- const { running, notRunning } = await import(
- "./__fixtures__/checking/runningState"
- );
+ const { running, notRunning } =
+ await import("./__fixtures__/checking/runningState");
expect(isEntryRunning(running)).toBe(true);
expect(isEntryRunning(notRunning)).toBe(false);
});
it("should determine entry running state (nested)", async () => {
- const { runningNested, stoppedNested } = await import(
- "./__fixtures__/checking/runningState"
- );
+ const { runningNested, stoppedNested } =
+ await import("./__fixtures__/checking/runningState");
expect(isEntryRunning(runningNested)).toBe(true);
expect(isEntryRunning(stoppedNested)).toBe(false);
@@ -83,9 +76,8 @@ describe("isEntryRunning", () => {
describe("getRunningEntry", () => {
it("should find running entry", async () => {
- const { input, runningEntry } = await import(
- "./__fixtures__/checking/shouldFindRunningEntry"
- );
+ const { input, runningEntry } =
+ await import("./__fixtures__/checking/shouldFindRunningEntry");
const output = getRunningEntry(input);
@@ -93,9 +85,8 @@ describe("getRunningEntry", () => {
});
it("should find nested running entry", async () => {
- const { input, runningEntry } = await import(
- "./__fixtures__/checking/shouldFindRunningEntryNested"
- );
+ const { input, runningEntry } =
+ await import("./__fixtures__/checking/shouldFindRunningEntryNested");
const output = getRunningEntry(input);
@@ -103,9 +94,8 @@ describe("getRunningEntry", () => {
});
it("should not find running entry", async () => {
- const { input } = await import(
- "./__fixtures__/checking/shouldNotFindRunningEntry"
- );
+ const { input } =
+ await import("./__fixtures__/checking/shouldNotFindRunningEntry");
const output = getRunningEntry(input);
@@ -115,17 +105,15 @@ describe("getRunningEntry", () => {
describe("isKeepRunning", () => {
it("should show keep running", async () => {
- const { input } = await import(
- "./__fixtures__/checking/shouldBeRunning"
- );
+ const { input } =
+ await import("./__fixtures__/checking/shouldBeRunning");
expect(isKeepRunning(input)).toBe(true);
});
it("should show keep not running", async () => {
- const { input } = await import(
- "./__fixtures__/checking/shouldNotBeRunning"
- );
+ const { input } =
+ await import("./__fixtures__/checking/shouldNotBeRunning");
expect(isKeepRunning(input)).toBe(false);
});
@@ -133,9 +121,8 @@ describe("isKeepRunning", () => {
describe("getEntryDuration", () => {
it("should get entry duration", async () => {
- const { input, currentTime, durationMs } = await import(
- "./__fixtures__/duration/shouldGetEntryDuration"
- );
+ const { input, currentTime, durationMs } =
+ await import("./__fixtures__/duration/shouldGetEntryDuration");
const output = getEntryDuration(input, currentTime);
@@ -143,9 +130,8 @@ describe("getEntryDuration", () => {
});
it("duration of non started entry should be zero", async () => {
- const { input, currentTime, durationMs } = await import(
- "./__fixtures__/duration/nonStartedZeroDuration"
- );
+ const { input, currentTime, durationMs } =
+ await import("./__fixtures__/duration/nonStartedZeroDuration");
const output = getEntryDuration(input, currentTime);
@@ -153,9 +139,8 @@ describe("getEntryDuration", () => {
});
it("duration should include children", async () => {
- const { input, currentTime, expected } = await import(
- "./__fixtures__/duration/durationIncludeChildren"
- );
+ const { input, currentTime, expected } =
+ await import("./__fixtures__/duration/durationIncludeChildren");
const output = getEntryDuration(input, currentTime);
@@ -163,9 +148,8 @@ describe("getEntryDuration", () => {
});
it("duration should use current as end for unfinished entries", async () => {
- const { input, endTime, durationMs } = await import(
- "./__fixtures__/duration/currentEndUnfinished"
- );
+ const { input, endTime, durationMs } =
+ await import("./__fixtures__/duration/currentEndUnfinished");
const output = getEntryDuration(input, endTime);
@@ -175,9 +159,8 @@ describe("getEntryDuration", () => {
describe("getTotalDuration", () => {
it("should get total duration", async () => {
- const { input, currentTime, expected } = await import(
- "./__fixtures__/duration/totalDuration"
- );
+ const { input, currentTime, expected } =
+ await import("./__fixtures__/duration/totalDuration");
const output = getTotalDuration(input, currentTime);
@@ -187,17 +170,15 @@ describe("getTotalDuration", () => {
describe("getUniqueEntryHash", () => {
it("hash should match when content matches", async () => {
- const { left, right } = await import(
- "./__fixtures__/hashing/hashMatches"
- );
+ const { left, right } =
+ await import("./__fixtures__/hashing/hashMatches");
expect(getUniqueEntryHash(left)).toBe(getUniqueEntryHash(right));
});
it("hash shouldn't match when content is different", async () => {
- const { left, right } = await import(
- "./__fixtures__/hashing/hashDoesNotMatch"
- );
+ const { left, right } =
+ await import("./__fixtures__/hashing/hashDoesNotMatch");
expect(getUniqueEntryHash(left)).not.toBe(getUniqueEntryHash(right));
});
diff --git a/src/timekeep/sort.test.ts b/src/timekeep/sort.test.ts
index 8c0857b..cfc32a1 100644
--- a/src/timekeep/sort.test.ts
+++ b/src/timekeep/sort.test.ts
@@ -9,9 +9,8 @@ import { getEntriesSorted } from "./sort";
describe("getEntriesSorted", () => {
it("should be in reverse order", async () => {
- const { input, expected } = await import(
- "./__fixtures__/ordering/reverseOrder"
- );
+ const { input, expected } =
+ await import("./__fixtures__/ordering/reverseOrder");
const settings: TimekeepSettings = defaultSettings;
settings.sortOrder = SortOrder.REVERSE_INSERTION;
@@ -21,9 +20,8 @@ describe("getEntriesSorted", () => {
});
it("should be in newest first order", async () => {
- const { input, expected } = await import(
- "./__fixtures__/ordering/newestFirstOrder"
- );
+ const { input, expected } =
+ await import("./__fixtures__/ordering/newestFirstOrder");
const settings: TimekeepSettings = defaultSettings;
settings.sortOrder = SortOrder.NEWEST_START;
@@ -33,9 +31,8 @@ describe("getEntriesSorted", () => {
});
it("should be in newest last order", async () => {
- const { input, expected } = await import(
- "./__fixtures__/ordering/newestLastOrder"
- );
+ const { input, expected } =
+ await import("./__fixtures__/ordering/newestLastOrder");
const settings: TimekeepSettings = defaultSettings;
settings.sortOrder = SortOrder.OLDEST_START;
@@ -45,9 +42,8 @@ describe("getEntriesSorted", () => {
});
it("should be in newest last order with nulls first", async () => {
- const { input, expected } = await import(
- "./__fixtures__/ordering/newestLastNullsFirst"
- );
+ const { input, expected } =
+ await import("./__fixtures__/ordering/newestLastNullsFirst");
const settings: TimekeepSettings = defaultSettings;
settings.sortOrder = SortOrder.OLDEST_START;
@@ -58,9 +54,8 @@ describe("getEntriesSorted", () => {
});
it("order should not change", async () => {
- const { input, expected } = await import(
- "./__fixtures__/ordering/orderShouldNotChange"
- );
+ const { input, expected } =
+ await import("./__fixtures__/ordering/orderShouldNotChange");
const settings: TimekeepSettings = defaultSettings;
settings.sortOrder = SortOrder.INSERTION;
diff --git a/src/timekeep/start.test.ts b/src/timekeep/start.test.ts
index 4f85edb..24d1af2 100644
--- a/src/timekeep/start.test.ts
+++ b/src/timekeep/start.test.ts
@@ -3,9 +3,8 @@ import { startNewEntry, startNewNestedEntry } from "./start";
describe("startNewEntry", () => {
it("starting a new entry should stop any running entries", async () => {
- const { currentTime, stopped, name, input, expected } = await import(
- "./__fixtures__/manipulating/start_entry/startShouldStopRunning"
- );
+ const { currentTime, stopped, name, input, expected } =
+ await import("./__fixtures__/manipulating/start_entry/startShouldStopRunning");
const output = startNewEntry(name, currentTime, input);
const stoppedEntry = output.find((entry) => entry.id === stopped);
@@ -19,9 +18,8 @@ describe("startNewEntry", () => {
});
it("starting a new entry should add a new entry", async () => {
- const { currentTime, name, input, expected } = await import(
- "./__fixtures__/manipulating/start_entry/startShouldStopRunning"
- );
+ const { currentTime, name, input, expected } =
+ await import("./__fixtures__/manipulating/start_entry/startShouldStopRunning");
const output = startNewEntry(name, currentTime, input);
expect(stripEntriesRuntimeData(output)).toEqual(
@@ -32,9 +30,8 @@ describe("startNewEntry", () => {
describe("startNewNestedEntry", () => {
it("starting a new entry should stop any running entries", async () => {
- const { currentTime, targetEntry, input, expected } = await import(
- "./__fixtures__/manipulating/start_entry/startNotStartedEntry"
- );
+ const { currentTime, targetEntry, input, expected } =
+ await import("./__fixtures__/manipulating/start_entry/startNotStartedEntry");
const output = startNewNestedEntry(currentTime, targetEntry.id, input);
expect(stripEntriesRuntimeData(output)).toEqual(
@@ -43,9 +40,8 @@ describe("startNewNestedEntry", () => {
});
it("starting a new entry within a folder should create a subentry", async () => {
- const { currentTime, targetEntry, input, expected } = await import(
- "./__fixtures__/manipulating/start_entry/startNestedFolderEntry"
- );
+ const { currentTime, targetEntry, input, expected } =
+ await import("./__fixtures__/manipulating/start_entry/startNestedFolderEntry");
const output = startNewNestedEntry(currentTime, targetEntry.id, input);
expect(stripEntriesRuntimeData(output)).toEqual(
@@ -54,9 +50,8 @@ describe("startNewNestedEntry", () => {
});
it("starting a new entry should stop any running entries", async () => {
- const { currentTime, targetEntry, input, expected } = await import(
- "./__fixtures__/manipulating/start_entry/startNotStartedEntry"
- );
+ const { currentTime, targetEntry, input, expected } =
+ await import("./__fixtures__/manipulating/start_entry/startNotStartedEntry");
const output = startNewNestedEntry(currentTime, targetEntry.id, input);
expect(stripEntriesRuntimeData(output)).toEqual(
@@ -65,9 +60,8 @@ describe("startNewNestedEntry", () => {
});
it("starting a new entry should stop any running entries", async () => {
- const { currentTime, targetEntryId, input, expected } = await import(
- "./__fixtures__/manipulating/start_entry/startNestedNonExistent"
- );
+ const { currentTime, targetEntryId, input, expected } =
+ await import("./__fixtures__/manipulating/start_entry/startNestedNonExistent");
const output = startNewNestedEntry(currentTime, targetEntryId, input);
expect(stripEntriesRuntimeData(output)).toEqual(
@@ -77,9 +71,7 @@ describe("startNewNestedEntry", () => {
it("starting a new entry should stop any running entries", async () => {
const { currentTime, stopped, targetEntry, input, expected } =
- await import(
- "./__fixtures__/manipulating/start_entry/startNestedShouldStopRunning"
- );
+ await import("./__fixtures__/manipulating/start_entry/startNestedShouldStopRunning");
const output = startNewNestedEntry(currentTime, targetEntry.id, input);
@@ -97,9 +89,8 @@ describe("startNewNestedEntry", () => {
});
it("starting a new entry should add a new entry", async () => {
- const { currentTime, targetEntry, input, expected } = await import(
- "./__fixtures__/manipulating/start_entry/startNestedShouldStopRunning"
- );
+ const { currentTime, targetEntry, input, expected } =
+ await import("./__fixtures__/manipulating/start_entry/startNestedShouldStopRunning");
const output = startNewNestedEntry(currentTime, targetEntry.id, input);
expect(stripEntriesRuntimeData(output)).toEqual(
diff --git a/src/timekeep/update.test.ts b/src/timekeep/update.test.ts
index 298fb42..9b6650c 100644
--- a/src/timekeep/update.test.ts
+++ b/src/timekeep/update.test.ts
@@ -10,9 +10,7 @@ import {
describe("updateEntry", () => {
it("updating existing entry should succeed", async () => {
const { entries, entryToUpdate, updatedEntry, expectedEntries } =
- await import(
- "./__fixtures__/manipulating/update_entry/updateEntry"
- );
+ await import("./__fixtures__/manipulating/update_entry/updateEntry");
const updated = updateEntry(entries, entryToUpdate.id, updatedEntry);
expect(updated).toEqual(expectedEntries);
@@ -21,9 +19,8 @@ describe("updateEntry", () => {
describe("setEntryCollapsed", () => {
it("should update group collapse state when set to true", async () => {
- const { input } = await import(
- "./__fixtures__/manipulating/collapse/shouldUpdateCollapseTrue"
- );
+ const { input } =
+ await import("./__fixtures__/manipulating/collapse/shouldUpdateCollapseTrue");
const collapsed = setEntryCollapsed(input, true);
@@ -33,9 +30,8 @@ describe("setEntryCollapsed", () => {
});
it("collapsed state should be undefined when false", async () => {
- const { input } = await import(
- "./__fixtures__/manipulating/collapse/shouldUpdateCollapseFalse"
- );
+ const { input } =
+ await import("./__fixtures__/manipulating/collapse/shouldUpdateCollapseFalse");
const collapsed = setEntryCollapsed(input, false);
expect(collapsed.subEntries).not.toBeNull();
@@ -43,9 +39,8 @@ describe("setEntryCollapsed", () => {
});
it("should not set collapse state on single entry", async () => {
- const { input } = await import(
- "./__fixtures__/manipulating/collapse/shouldNotCollapseSingleEntry"
- );
+ const { input } =
+ await import("./__fixtures__/manipulating/collapse/shouldNotCollapseSingleEntry");
const collapsed = setEntryCollapsed(input, true);
@@ -56,9 +51,8 @@ describe("setEntryCollapsed", () => {
describe("stopRunningEntries", () => {
it("should stop running entries", async () => {
- const { input, endTime, expected } = await import(
- "./__fixtures__/manipulating/stopping_entries/stopRunningEntries"
- );
+ const { input, endTime, expected } =
+ await import("./__fixtures__/manipulating/stopping_entries/stopRunningEntries");
const output = stopRunningEntries(input, endTime);
expect(output).toEqual(expected);
@@ -67,50 +61,44 @@ describe("stopRunningEntries", () => {
describe("removeEntry", () => {
it("remove on single entry should stay same if not target", async () => {
- const { entries, entryToRemove, expectedEntries } = await import(
- "./__fixtures__/manipulating/remove_entry/removeSingleEntry"
- );
+ const { entries, entryToRemove, expectedEntries } =
+ await import("./__fixtures__/manipulating/remove_entry/removeSingleEntry");
const updated = removeEntry(entries, entryToRemove);
expect(updated).toEqual(expectedEntries);
});
it("should be able to remove entry", async () => {
- const { entries, entryToRemove, expectedEntries } = await import(
- "./__fixtures__/manipulating/remove_entry/removeEntrySuccess"
- );
+ const { entries, entryToRemove, expectedEntries } =
+ await import("./__fixtures__/manipulating/remove_entry/removeEntrySuccess");
const updated = removeEntry(entries, entryToRemove);
expect(updated).toEqual(expectedEntries);
});
it("should be able to remove nested entry", async () => {
- const { entries, entryToRemove, expectedEntries } = await import(
- "./__fixtures__/manipulating/remove_entry/removeNestedEntry"
- );
+ const { entries, entryToRemove, expectedEntries } =
+ await import("./__fixtures__/manipulating/remove_entry/removeNestedEntry");
const updated = removeEntry(entries, entryToRemove);
expect(updated).toEqual(expectedEntries);
});
it("should collapse groups with only one entry on remove", async () => {
- const { entries, entryToRemove, expectedEntries } = await import(
- "./__fixtures__/manipulating/remove_entry/removeEntryCollapse"
- );
+ const { entries, entryToRemove, expectedEntries } =
+ await import("./__fixtures__/manipulating/remove_entry/removeEntryCollapse");
const updated = removeEntry(entries, entryToRemove);
expect(updated).toEqual(expectedEntries);
});
it("should collapse groups with only one entry on remove (single)", async () => {
- const { entries, entryToRemove, expectedEntries } = await import(
- "./__fixtures__/manipulating/remove_entry/removeEntryCollapseSingle"
- );
+ const { entries, entryToRemove, expectedEntries } =
+ await import("./__fixtures__/manipulating/remove_entry/removeEntryCollapseSingle");
const updated = removeEntry(entries, entryToRemove);
expect(updated).toEqual(expectedEntries);
});
it("should not collapse folder on empty entries", async () => {
- const { entries, entryToRemove, expectedEntries } = await import(
- "./__fixtures__/manipulating/remove_entry/removeEntryFolder"
- );
+ const { entries, entryToRemove, expectedEntries } =
+ await import("./__fixtures__/manipulating/remove_entry/removeEntryFolder");
const updated = removeEntry(entries, entryToRemove);
expect(updated).toEqual(expectedEntries);
});
@@ -118,9 +106,8 @@ describe("removeEntry", () => {
describe("removeSubEntry", () => {
it("attempting to remove sub entry on non group should do nothing", async () => {
- const { parent, entryToRemove } = await import(
- "./__fixtures__/manipulating/remove_entry/removeEntry"
- );
+ const { parent, entryToRemove } =
+ await import("./__fixtures__/manipulating/remove_entry/removeEntry");
const output = removeSubEntry(parent, entryToRemove);
expect(output).toEqual(parent);
});
diff --git a/test-vault/Obsidian Timekeep.md b/test-vault/Obsidian Timekeep.md
index 1534db2..7daa32b 100644
--- a/test-vault/Obsidian Timekeep.md
+++ b/test-vault/Obsidian Timekeep.md
@@ -28,7 +28,7 @@ dv.span(totalRunningDuration);
```timekeep
-{"entries":[{"name":"Block 4 1","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":"2024-03-17T01:33:51.000Z","endTime":"2024-03-17T01:33:55.000Z","subEntries":null},{"name":"Part 2","startTime":"2024-03-24T05:20:54.000Z","endTime":"2024-03-24T05:20:55.000Z","subEntries":null}]},{"name":"Part 2","startTime":"2024-03-24T05:20:51.498Z","endTime":"2024-03-24T05:20:53.370Z","subEntries":null},{"name":"Part 3","startTime":"2024-03-24T05:20:56.000Z","endTime":"2024-03-24T05:21:30.000Z","subEntries":null}]},{"name":"Block 5","startTime":"2024-03-17T01:33:57.375Z","endTime":"2024-03-17T01:33:58.867Z","subEntries":null},{"name":"Block 8","startTime":"2024-03-17T01:37:33.417Z","endTime":"2024-03-17T01:37:35.048Z","subEntries":null},{"name":"Block 9","startTime":"2024-03-17T01:37:44.470Z","endTime":"2024-03-17T01:37:45.141Z","subEntries":null},{"name":"Block 6","startTime":"2024-03-17T02:00:38.491Z","endTime":"2024-03-17T02:00:39.208Z","subEntries":null},{"name":"Block 7","startTime":"2024-03-17T02:00:39.528Z","endTime":"2024-03-17T02:00:39.823Z","subEntries":null},{"name":"Block 8","startTime":"2024-03-17T02:00:40.118Z","endTime":"2024-03-17T02:00:40.428Z","subEntries":null},{"name":"Block 9","startTime":"2024-03-17T02:00:40.814Z","endTime":"2024-03-17T02:00:41.066Z","subEntries":null},{"name":"Block 10","startTime":"2024-03-17T02:00:41.327Z","endTime":"2024-03-17T02:00:41.507Z","subEntries":null},{"name":"Block 11","startTime":"2024-03-17T02:00:41.783Z","endTime":"2024-03-17T02:00:42.009Z","subEntries":null},{"name":"Block 13","startTime":"2024-03-17T02:57:16.000Z","endTime":"2024-03-17T02:57:16.000Z","subEntries":null},{"name":"Block 14","startTime":"2024-03-17T02:57:19.000Z","endTime":"2024-03-17T02:57:19.000Z","subEntries":null},{"name":"Block 15","startTime":"2024-03-17T02:57:24.000Z","endTime":"2024-03-17T02:57:24.000Z","subEntries":null},{"name":"Block 16 Supper reallly long name that leaves the table bounds","startTime":"2024-03-17T02:57:25.000Z","endTime":"2024-03-17T02:57:25.000Z","subEntries":null},{"name":"Block 17","startTime":"2024-03-17T03:29:53.766Z","endTime":"2024-03-17T03:29:54.925Z","subEntries":null},{"name":"Block 18","startTime":"2024-03-17T03:30:26.368Z","endTime":"2024-03-17T03:30:34.257Z","subEntries":null},{"name":"New Block","startTime":"2024-03-17T03:32:42.451Z","endTime":"2024-03-17T03:33:41.318Z","subEntries":null},{"name":"Block 20","startTime":"2024-03-17T03:37:45.552Z","endTime":"2024-03-17T03:37:47.568Z","subEntries":null},{"name":"Block 21","startTime":"2024-03-17T03:38:14.405Z","endTime":"2024-03-17T03:38:18.039Z","subEntries":null},{"name":"Block 21","startTime":"2024-03-17T05:01:30.553Z","endTime":"2024-03-17T05:01:36.012Z","subEntries":null},{"name":"Block 22","startTime":"2024-03-17T05:01:37.365Z","endTime":"2024-03-17T05:01:40.180Z","subEntries":null},{"name":"Block 23","startTime":"2024-03-24T03:44:16.500Z","endTime":"2024-03-24T03:44:18.168Z","subEntries":null},{"name":"Block 24","startTime":"2024-03-24T03:44:34.125Z","endTime":"2024-03-24T03:44:35.421Z","subEntries":null},{"name":"Block 25","startTime":"2024-03-24T03:44:38.073Z","endTime":"2024-03-24T03:44:50.001Z","subEntries":null},{"name":"Block 26","startTime":"2024-03-24T04:05:50.912Z","endTime":"2024-03-24T04:05:53.243Z","subEntries":null},{"name":"This is my block","startTime":"2024-03-24T04:09:02.146Z","endTime":"2024-03-24T04:09:06.662Z","subEntries":null},{"name":"Block 28","startTime":"2024-03-24T04:15:47.980Z","endTime":"2024-03-24T04:15:48.924Z","subEntries":null},{"name":"Block 29","startTime":"2024-03-24T04:15:49.518Z","endTime":"2024-03-24T04:15:50.561Z","subEntries":null},{"name":"Block 30","startTime":"2024-03-24T04:15:51.654Z","endTime":"2024-03-24T04:15:52.560Z","subEntries":null},{"name":"Block 31","startTime":"2024-03-24T04:17:34.822Z","endTime":"2024-03-24T04:17:35.571Z","subEntries":null},{"name":"Block 32","startTime":"2024-03-24T04:17:36.303Z","endTime":"2024-03-24T04:17:36.936Z","subEntries":null},{"name":"Block 33","startTime":"2024-03-24T04:17:43.413Z","endTime":"2024-03-24T04:17:44.142Z","subEntries":null},{"name":"Block 34","startTime":"2024-03-24T04:17:44.916Z","endTime":"2024-03-24T04:17:45.552Z","subEntries":null},{"name":"Block 35","startTime":"2024-03-24T04:17:52.315Z","endTime":"2024-03-24T04:17:52.971Z","subEntries":null},{"name":"Block 36","startTime":"2024-03-24T04:17:53.590Z","endTime":"2024-03-24T04:17:54.318Z","subEntries":null},{"name":"Block 37","startTime":"2024-03-24T04:17:54.858Z","endTime":"2024-03-24T04:17:56.340Z","subEntries":null},{"name":"Block 38","startTime":"2024-03-24T04:21:19.540Z","endTime":"2024-03-24T04:21:20.803Z","subEntries":null},{"name":"Block 39","startTime":"2024-03-24T04:21:21.876Z","endTime":"2024-03-24T04:21:24.420Z","subEntries":null},{"name":"Block 40","startTime":"2024-03-24T04:22:14.073Z","endTime":"2024-03-24T04:22:14.793Z","subEntries":null},{"name":"Block 41","startTime":"2024-03-24T04:22:15.463Z","endTime":"2024-03-24T04:22:16.016Z","subEntries":null},{"name":"Block 42","startTime":"2024-03-24T04:28:14.807Z","endTime":"2024-03-24T04:28:15.565Z","subEntries":null},{"name":"Block 43","startTime":"2024-03-24T04:28:39.885Z","endTime":"2024-03-24T04:28:42.912Z","subEntries":null},{"name":"Block 44","startTime":"2024-03-24T04:28:44.495Z","endTime":"2024-03-24T04:28:45.736Z","subEntries":null},{"name":"Block 44","startTime":"2024-03-24T04:30:43.184Z","endTime":"2024-03-24T04:30:45.307Z","subEntries":null},{"name":"Block 45","startTime":"2024-03-24T04:30:46.565Z","endTime":"2024-03-24T04:30:47.749Z","subEntries":null},{"name":"Block 46","startTime":"2024-03-24T04:30:48.502Z","endTime":"2024-03-24T04:30:49.762Z","subEntries":null},{"name":"Block 47","startTime":"2024-03-24T04:31:01.261Z","endTime":"2024-03-24T04:31:12.831Z","subEntries":null},{"name":"Block 48","startTime":"2024-03-24T04:31:14.603Z","endTime":"2024-03-24T04:31:16.328Z","subEntries":null},{"name":"Block 49","startTime":"2024-03-24T04:31:17.249Z","endTime":"2024-03-24T04:31:18.115Z","subEntries":null},{"name":"Block 50","startTime":"2024-03-24T04:31:19.266Z","endTime":"2024-03-24T04:31:20.199Z","subEntries":null},{"name":"Block 51","startTime":"2024-03-24T04:33:17.046Z","endTime":"2024-03-24T04:33:17.646Z","subEntries":null},{"name":"Block 52","startTime":"2024-03-24T04:33:18.253Z","endTime":"2024-03-24T04:33:18.969Z","subEntries":null},{"name":"Block 53","startTime":"2024-03-24T04:33:19.684Z","endTime":"2024-03-24T04:33:21.706Z","subEntries":null},{"name":"Block 54","startTime":"2024-03-24T04:33:22.293Z","endTime":"2024-03-24T04:33:22.674Z","subEntries":null},{"name":"Block 55","startTime":"2024-03-24T06:35:53.785Z","endTime":"2024-03-24T06:35:54.322Z","subEntries":null},{"name":"Block 56","startTime":"2024-03-24T06:35:54.693Z","endTime":"2024-03-24T06:35:54.864Z","subEntries":null},{"name":"Block 57","startTime":"2024-03-24T06:35:55.031Z","endTime":"2024-03-24T06:35:55.190Z","subEntries":null},{"name":"Block 58","startTime":"2024-03-24T06:35:55.631Z","endTime":"2024-03-24T06:35:57.836Z","subEntries":null},{"name":"Block 59","startTime":"2024-03-24T06:36:06.400Z","endTime":"2024-03-24T06:36:08.452Z","subEntries":null},{"name":"Block 60","startTime":"2024-03-24T06:36:53.108Z","endTime":"2024-03-24T06:36:53.791Z","subEntries":null},{"name":"Block 61","startTime":"2024-03-24T06:42:04.593Z","endTime":"2024-03-24T06:42:05.961Z","subEntries":null},{"name":"Block 62","startTime":"2024-03-24T06:42:30.789Z","endTime":"2024-03-24T06:42:31.609Z","subEntries":null},{"name":"Block 63","startTime":"2024-05-01T08:17:37.721Z","endTime":"2024-05-01T08:20:49.070Z","subEntries":null},{"name":"Test dwada uidawh dawhiud hawhiduia uhiwdiuhhauiw dhawd ihuawuidh awudhiu ahiwudhiu ahwiudihu awhiudhiu hiud ihuawhid hiuahiud ihwad","startTime":"2024-05-01T08:20:56.478Z","endTime":"2024-05-01T08:21:01.643Z","subEntries":null},{"name":"Test","startTime":"2024-05-01T08:21:03.005Z","endTime":"2024-05-01T08:21:35.567Z","subEntries":null},{"name":"tEWADAWDAWDAWD","startTime":"2024-05-01T08:21:37.838Z","endTime":"2024-05-01T08:22:25.344Z","subEntries":null},{"name":"Tewadwdawd","startTime":"2024-05-01T08:22:27.180Z","endTime":"2024-05-01T08:22:28.683Z","subEntries":null},{"name":"awdawd","startTime":"2024-05-01T08:22:30.186Z","endTime":"2024-05-01T08:22:36.241Z","subEntries":null},{"name":"Block 69","startTime":"2024-05-01T08:22:36.760Z","endTime":"2024-05-01T08:22:37.361Z","subEntries":null},{"name":"Block 70","startTime":"2024-05-01T08:22:37.695Z","endTime":"2024-05-01T08:22:38.428Z","subEntries":null},{"name":"Block 71","startTime":"2024-05-01T08:22:39.077Z","endTime":"2024-05-01T08:22:40.410Z","subEntries":null},{"name":"Block 72","startTime":"2024-05-01T08:22:40.991Z","endTime":"2024-05-01T08:22:41.538Z","subEntries":null},{"name":"Block 73","startTime":"2024-05-01T08:22:41.943Z","endTime":"2024-05-01T08:22:42.729Z","subEntries":null},{"name":"Block 74","startTime":"2024-05-01T08:22:43.160Z","endTime":"2024-05-01T08:22:44.806Z","subEntries":null},{"name":"Block 75","startTime":"2024-05-01T08:22:45.423Z","endTime":"2024-05-01T08:22:46.040Z","subEntries":null},{"name":"Block 76","startTime":"2024-05-01T08:22:49.574Z","endTime":"2024-05-01T08:22:50.712Z","subEntries":null},{"name":"Block 77","startTime":"2024-05-01T08:22:51.298Z","endTime":"2024-05-01T08:23:06.033Z","subEntries":null},{"name":"Block 78","startTime":"2024-05-01T08:23:07.620Z","endTime":"2024-05-01T08:23:08.124Z","subEntries":null},{"name":"Block 79","startTime":"2024-05-01T08:23:09.529Z","endTime":"2024-05-01T08:23:09.984Z","subEntries":null},{"name":"Block 80","startTime":"2024-05-01T08:23:44.681Z","endTime":"2024-05-01T08:23:56.772Z","subEntries":null},{"name":"Block 82","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":"2024-07-21T07:12:56.433Z","endTime":"2024-07-21T07:13:33.907Z","subEntries":null},{"name":"Part 2","startTime":"2024-07-21T07:24:44.769Z","endTime":"2024-07-21T07:24:48.149Z","subEntries":null},{"name":"Part 3","startTime":"2024-07-21T09:06:54.527Z","endTime":"2024-07-21T09:06:57.360Z","subEntries":null},{"name":"Part 4","startTime":"2024-07-21T09:07:10.820Z","endTime":"2024-07-21T09:07:11.426Z","subEntries":null}]},{"name":"Block 82","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":"2024-07-21T07:13:34.743Z","endTime":"2024-07-21T07:14:13.095Z","subEntries":null},{"name":"Part 2","startTime":"2024-07-21T07:14:13.562Z","endTime":"2024-07-21T07:14:14.679Z","subEntries":null},{"name":"Part 3","startTime":"2024-07-21T07:14:15.566Z","endTime":"2024-07-21T07:14:15.970Z","subEntries":null},{"name":"Part 4","startTime":"2024-07-21T09:06:52.510Z","endTime":"2024-07-21T09:06:53.351Z","subEntries":null},{"name":"Part 5","startTime":"2024-07-21T09:07:19.089Z","endTime":"2024-07-21T09:07:20.066Z","subEntries":null}]},{"name":"Block 83","startTime":"2024-07-21T09:07:21.923Z","endTime":"2024-07-21T09:07:22.516Z","subEntries":null},{"name":"Block 81","startTime":"2025-01-07T02:20:00.000Z","endTime":"2025-01-07T03:20:00.000Z","subEntries":null},{"name":"Block 85 [[#Test]] ","startTime":"2025-01-07T04:18:19.000Z","endTime":"2025-01-07T03:18:20.000Z","subEntries":null}]}
+{"entries":[{"name":"Block 4 1","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":"2024-03-17T01:33:51.000Z","endTime":"2024-09-17T07:50:55.000Z","subEntries":null},{"name":"Part 2","startTime":"2024-03-24T05:20:54.000Z","endTime":"2024-03-24T05:20:55.000Z","subEntries":null}]},{"name":"Part 2","startTime":"2024-03-24T05:20:51.498Z","endTime":"2024-03-24T05:20:53.370Z","subEntries":null},{"name":"Part 3","startTime":"2024-03-24T05:20:56.000Z","endTime":"2024-03-24T05:21:30.000Z","subEntries":null}]},{"name":"Block 5","startTime":"2024-03-17T01:33:57.375Z","endTime":"2024-03-17T01:33:58.867Z","subEntries":null},{"name":"Block 8","startTime":"2024-03-17T01:37:33.000Z","endTime":"2024-03-17T06:37:35.000Z","subEntries":null},{"name":"Block 9","startTime":"2024-03-17T01:37:44.470Z","endTime":"2024-03-17T01:37:45.141Z","subEntries":null},{"name":"Block 6","startTime":"2024-03-17T02:00:38.491Z","endTime":"2024-03-17T02:00:39.208Z","subEntries":null},{"name":"Block 7","startTime":"2024-03-17T02:00:39.528Z","endTime":"2024-03-17T02:00:39.823Z","subEntries":null},{"name":"Block 8","startTime":"2024-03-17T02:00:40.118Z","endTime":"2024-03-17T02:00:40.428Z","subEntries":null},{"name":"Block 9","startTime":"2024-03-17T02:00:40.814Z","endTime":"2024-03-17T02:00:41.066Z","subEntries":null},{"name":"Block 10","startTime":"2024-03-17T02:00:41.327Z","endTime":"2024-03-17T02:00:41.507Z","subEntries":null},{"name":"Block 11","startTime":"2024-03-17T02:00:41.783Z","endTime":"2024-03-17T02:00:42.009Z","subEntries":null},{"name":"Block 13","startTime":"2024-03-17T02:57:16.000Z","endTime":"2024-03-17T02:57:16.000Z","subEntries":null},{"name":"Block 14","startTime":"2024-03-17T02:57:19.000Z","endTime":"2024-03-17T02:57:19.000Z","subEntries":null},{"name":"Block 15","startTime":"2024-03-17T02:57:24.000Z","endTime":"2024-03-17T02:57:24.000Z","subEntries":null},{"name":"Block 16 Supper reallly long name that leaves the table bounds","startTime":"2024-03-17T02:57:25.000Z","endTime":"2024-03-17T02:57:25.000Z","subEntries":null},{"name":"Block 17","startTime":"2024-03-17T03:29:53.766Z","endTime":"2024-03-17T03:29:54.925Z","subEntries":null},{"name":"Block 18","startTime":"2024-03-17T03:30:26.368Z","endTime":"2024-03-17T03:30:34.257Z","subEntries":null},{"name":"New Block","startTime":"2024-03-17T03:32:42.451Z","endTime":"2024-03-17T03:33:41.318Z","subEntries":null},{"name":"Block 20","startTime":"2024-03-17T03:37:45.552Z","endTime":"2024-03-17T03:37:47.568Z","subEntries":null},{"name":"Block 21","startTime":"2024-03-17T03:38:14.405Z","endTime":"2024-03-17T03:38:18.039Z","subEntries":null},{"name":"Block 21","startTime":"2024-03-17T05:01:30.553Z","endTime":"2024-03-17T05:01:36.012Z","subEntries":null},{"name":"Block 22","startTime":"2024-03-17T05:01:37.365Z","endTime":"2024-03-17T05:01:40.180Z","subEntries":null},{"name":"Block 23","startTime":"2024-03-24T03:44:16.500Z","endTime":"2024-03-24T03:44:18.168Z","subEntries":null},{"name":"Block 24","startTime":"2024-03-24T03:44:34.125Z","endTime":"2024-03-24T03:44:35.421Z","subEntries":null},{"name":"Block 25","startTime":"2024-03-24T03:44:38.073Z","endTime":"2024-03-24T03:44:50.001Z","subEntries":null},{"name":"Block 26","startTime":"2024-03-24T04:05:50.912Z","endTime":"2024-03-24T04:05:53.243Z","subEntries":null},{"name":"This is my block","startTime":"2024-03-24T04:09:02.146Z","endTime":"2024-03-24T04:09:06.662Z","subEntries":null},{"name":"Block 28","startTime":"2024-03-24T04:15:47.980Z","endTime":"2024-03-24T04:15:48.924Z","subEntries":null},{"name":"Block 29","startTime":"2024-03-24T04:15:49.518Z","endTime":"2024-03-24T04:15:50.561Z","subEntries":null},{"name":"Block 30","startTime":"2024-03-24T04:15:51.654Z","endTime":"2024-03-24T04:15:52.560Z","subEntries":null},{"name":"Block 31","startTime":"2024-03-24T04:17:34.822Z","endTime":"2024-03-24T04:17:35.571Z","subEntries":null},{"name":"Block 32","startTime":"2024-03-24T04:17:36.303Z","endTime":"2024-03-24T04:17:36.936Z","subEntries":null},{"name":"Block 33","startTime":"2024-03-24T04:17:43.413Z","endTime":"2024-03-24T04:17:44.142Z","subEntries":null},{"name":"Block 34","startTime":"2024-03-24T04:17:44.916Z","endTime":"2024-03-24T04:17:45.552Z","subEntries":null},{"name":"Block 35","startTime":"2024-03-24T04:17:52.315Z","endTime":"2024-03-24T04:17:52.971Z","subEntries":null},{"name":"Block 36","startTime":"2024-03-24T04:17:53.590Z","endTime":"2024-03-24T04:17:54.318Z","subEntries":null},{"name":"Block 37","startTime":"2024-03-24T04:17:54.858Z","endTime":"2024-03-24T04:17:56.340Z","subEntries":null},{"name":"Block 38","startTime":"2024-03-24T04:21:19.540Z","endTime":"2024-03-24T04:21:20.803Z","subEntries":null},{"name":"Block 39","startTime":"2024-03-24T04:21:21.876Z","endTime":"2024-03-24T04:21:24.420Z","subEntries":null},{"name":"Block 40","startTime":"2024-03-24T04:22:14.073Z","endTime":"2024-03-24T04:22:14.793Z","subEntries":null},{"name":"Block 41","startTime":"2024-03-24T04:22:15.463Z","endTime":"2024-03-24T04:22:16.016Z","subEntries":null},{"name":"Block 42","startTime":"2024-03-24T04:28:14.807Z","endTime":"2024-03-24T04:28:15.565Z","subEntries":null},{"name":"Block 43","startTime":"2024-03-24T04:28:39.885Z","endTime":"2024-03-24T04:28:42.912Z","subEntries":null},{"name":"Block 44","startTime":"2024-03-24T04:28:44.495Z","endTime":"2024-03-24T04:28:45.736Z","subEntries":null},{"name":"Block 44","startTime":"2024-03-24T04:30:43.184Z","endTime":"2024-03-24T04:30:45.307Z","subEntries":null},{"name":"Block 45","startTime":"2024-03-24T04:30:46.565Z","endTime":"2024-03-24T04:30:47.749Z","subEntries":null},{"name":"Block 46","startTime":"2024-03-24T04:30:48.502Z","endTime":"2024-03-24T04:30:49.762Z","subEntries":null},{"name":"Block 47","startTime":"2024-03-24T04:31:01.261Z","endTime":"2024-03-24T04:31:12.831Z","subEntries":null},{"name":"Block 48","startTime":"2024-03-24T04:31:14.603Z","endTime":"2024-03-24T04:31:16.328Z","subEntries":null},{"name":"Block 49","startTime":"2024-03-24T04:31:17.249Z","endTime":"2024-03-24T04:31:18.115Z","subEntries":null},{"name":"Block 50","startTime":"2024-03-24T04:31:19.266Z","endTime":"2024-03-24T04:31:20.199Z","subEntries":null},{"name":"Block 51","startTime":"2024-03-24T04:33:17.046Z","endTime":"2024-03-24T04:33:17.646Z","subEntries":null},{"name":"Block 52","startTime":"2024-03-24T04:33:18.253Z","endTime":"2024-03-24T04:33:18.969Z","subEntries":null},{"name":"Block 53","startTime":"2024-03-24T04:33:19.684Z","endTime":"2024-03-24T04:33:21.706Z","subEntries":null},{"name":"Block 54","startTime":"2024-03-24T04:33:22.293Z","endTime":"2024-03-24T04:33:22.674Z","subEntries":null},{"name":"Block 55","startTime":"2024-03-24T06:35:53.785Z","endTime":"2024-03-24T06:35:54.322Z","subEntries":null},{"name":"Block 56","startTime":"2024-03-24T06:35:54.693Z","endTime":"2024-03-24T06:35:54.864Z","subEntries":null},{"name":"Block 57","startTime":"2024-03-24T06:35:55.031Z","endTime":"2024-03-24T06:35:55.190Z","subEntries":null},{"name":"Block 58","startTime":"2024-03-24T06:35:55.631Z","endTime":"2024-03-24T06:35:57.836Z","subEntries":null},{"name":"Block 59","startTime":"2024-03-24T06:36:06.400Z","endTime":"2024-03-24T06:36:08.452Z","subEntries":null},{"name":"Block 60","startTime":"2024-03-24T06:36:53.108Z","endTime":"2024-03-24T06:36:53.791Z","subEntries":null},{"name":"Block 61","startTime":"2024-03-24T06:42:04.593Z","endTime":"2024-03-24T06:42:05.961Z","subEntries":null},{"name":"Block 62","startTime":"2024-03-24T06:42:30.789Z","endTime":"2024-03-24T06:42:31.609Z","subEntries":null},{"name":"Block 63","startTime":"2024-05-01T08:17:37.721Z","endTime":"2024-05-01T08:20:49.070Z","subEntries":null},{"name":"Test dwada uidawh dawhiud hawhiduia uhiwdiuhhauiw dhawd ihuawuidh awudhiu ahiwudhiu ahwiudihu awhiudhiu hiud ihuawhid hiuahiud ihwad","startTime":"2024-05-01T08:20:56.478Z","endTime":"2024-05-01T08:21:01.643Z","subEntries":null},{"name":"Test","startTime":"2024-05-01T08:21:03.005Z","endTime":"2024-05-01T08:21:35.567Z","subEntries":null},{"name":"tEWADAWDAWDAWD","startTime":"2024-05-01T08:21:37.838Z","endTime":"2024-05-01T08:22:25.344Z","subEntries":null},{"name":"Tewadwdawd","startTime":"2024-05-01T08:22:27.180Z","endTime":"2024-05-01T08:22:28.683Z","subEntries":null},{"name":"awdawd","startTime":"2024-05-01T08:22:30.186Z","endTime":"2024-05-01T08:22:36.241Z","subEntries":null},{"name":"Block 69","startTime":"2024-05-01T08:22:36.760Z","endTime":"2024-05-01T08:22:37.361Z","subEntries":null},{"name":"Block 70","startTime":"2024-05-01T08:22:37.695Z","endTime":"2024-05-01T08:22:38.428Z","subEntries":null},{"name":"Block 71","startTime":"2024-05-01T08:22:39.077Z","endTime":"2024-05-01T08:22:40.410Z","subEntries":null},{"name":"Block 72","startTime":"2024-05-01T08:22:40.991Z","endTime":"2024-05-01T08:22:41.538Z","subEntries":null},{"name":"Block 73","startTime":"2024-05-01T08:22:41.943Z","endTime":"2024-05-01T08:22:42.729Z","subEntries":null},{"name":"Block 74","startTime":"2024-05-01T08:22:43.160Z","endTime":"2024-05-01T08:22:44.806Z","subEntries":null},{"name":"Block 75","startTime":"2024-05-01T08:22:45.423Z","endTime":"2024-05-01T08:22:46.040Z","subEntries":null},{"name":"Block 76","startTime":"2024-05-01T08:22:49.574Z","endTime":"2024-05-01T08:22:50.712Z","subEntries":null},{"name":"Block 77","startTime":"2024-05-01T08:22:51.298Z","endTime":"2024-05-01T08:23:06.033Z","subEntries":null},{"name":"Block 78","startTime":"2024-05-01T08:23:07.620Z","endTime":"2024-05-01T08:23:08.124Z","subEntries":null},{"name":"Block 79","startTime":"2024-05-01T08:23:09.529Z","endTime":"2024-05-01T08:23:09.984Z","subEntries":null},{"name":"Block 80","startTime":"2024-05-01T08:23:44.681Z","endTime":"2024-05-01T08:23:56.772Z","subEntries":null},{"name":"Block 82","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":"2024-07-21T07:12:56.433Z","endTime":"2024-07-21T07:13:33.907Z","subEntries":null},{"name":"Part 2","startTime":"2024-07-21T07:24:44.769Z","endTime":"2024-07-21T07:24:48.149Z","subEntries":null},{"name":"Part 3","startTime":"2024-07-21T09:06:54.527Z","endTime":"2024-07-21T09:06:57.360Z","subEntries":null},{"name":"Part 4","startTime":"2024-07-21T09:07:10.820Z","endTime":"2024-07-21T09:07:11.426Z","subEntries":null}]},{"name":"Block 82","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":"2024-07-21T07:13:34.743Z","endTime":"2024-07-21T07:14:13.095Z","subEntries":null},{"name":"Part 2","startTime":"2024-07-21T07:14:13.562Z","endTime":"2024-07-21T07:14:14.679Z","subEntries":null},{"name":"Part 3","startTime":"2024-07-21T07:14:15.566Z","endTime":"2024-07-21T07:14:15.970Z","subEntries":null},{"name":"Part 4","startTime":"2024-07-21T09:06:52.510Z","endTime":"2024-07-21T09:06:53.351Z","subEntries":null},{"name":"Part 5","startTime":"2024-07-21T09:07:19.089Z","endTime":"2024-07-21T09:07:20.066Z","subEntries":null}]},{"name":"Block 83","startTime":"2024-07-21T09:07:21.923Z","endTime":"2024-07-21T09:07:22.516Z","subEntries":null},{"name":"Block 81","startTime":"2025-01-07T02:20:00.000Z","endTime":"2025-01-07T03:20:00.000Z","subEntries":null},{"name":"Block 85 [[#Test]] ","startTime":"2025-01-07T04:18:19.000Z","endTime":"2025-01-07T03:18:20.000Z","subEntries":null}]}
```
```timekeep
{"entries":[{"name":"Block 1","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":"2024-03-31T07:27:36.569Z","endTime":"2024-03-31T07:27:37.726Z","subEntries":null},{"name":"Part 2","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":"2024-03-31T07:27:42.846Z","endTime":"2024-03-31T07:27:44.468Z","subEntries":null},{"name":"Part 2","startTime":"2024-03-31T07:27:47.517Z","endTime":"2024-03-31T07:27:48.459Z","subEntries":null}]}]},{"name":"Block 2","startTime":"2024-03-31T07:27:38.418Z","endTime":"2024-03-31T07:27:38.917Z","subEntries":null},{"name":"Block 3","startTime":"2024-03-31T07:27:39.480Z","endTime":"2024-03-31T07:27:39.796Z","subEntries":null},{"name":"Block 4","startTime":"2024-03-31T07:27:40.088Z","endTime":"2024-03-31T07:27:40.331Z","subEntries":null},{"name":"Block 5","startTime":"2024-03-31T07:27:40.548Z","endTime":"2024-03-31T07:27:40.795Z","subEntries":null},{"name":"Block 6","startTime":"2024-03-31T07:27:41.037Z","endTime":"2024-03-31T07:27:41.520Z","subEntries":null}]}