Merge pull request #72 from jacobtread/refactor-pdf-make-renderer

Refactor pdf make renderer
This commit is contained in:
Jacob 2026-03-25 19:53:53 +13:00 committed by GitHub
commit f6f5ebb342
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 624 additions and 1051 deletions

View file

@ -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' },
}
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" },
};

546
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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 (
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.heading}>
<Text style={styles.title}>{title}</Text>
<Text style={styles.title}>Timesheet</Text>
</View>
<View style={styles.details}>
<TimesheetPdfDetailField
name="Date"
value={currentDate}
styles={styles}
/>
<TimesheetPdfDetailField
name="Total Duration"
value={totalDuration}
styles={styles}
/>
<TimesheetPdfDetailField
name="Total Duration (hours)"
value={totalDurationShort}
styles={styles}
/>
</View>
<TimesheetPdfTable
data={data}
currentTime={currentTime}
totalDuration={totalDuration}
settings={settings}
styles={styles}
/>
<Text style={styles.footNote} wrap={false}>
{footnote}
</Text>
</Page>
</Document>
);
}

View file

@ -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 (
<Text style={styles.detailsFieldValue}>
<Text style={styles.detailsFieldName}>{name}: </Text>
{value}
</Text>
);
}

View file

@ -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 = (
<View style={[styles.tableRow, styles.tableRowHeader]} wrap={false}>
<Text style={[styles.tableCell, styles.tableCellBlock]}>
Block Name
</Text>
<Text style={[styles.tableCell, styles.tableCellTime]}>
Start Time
</Text>
<Text style={[styles.tableCell, styles.tableCellTime]}>
End Time
</Text>
<Text style={[styles.tableCell, styles.tableCellTime]}>
Duration
</Text>
</View>
);
// Render the rows of the table
const renderEntries = data.entries.map((entry, index) => (
<TimesheetPdfTableRow
entry={entry}
key={index}
currentTime={currentTime}
settings={settings}
styles={styles}
/>
));
// Render the table footer
const renderFooter = (
<View style={[styles.tableRow, styles.tableRowHeader]} wrap={false}>
<Text style={[styles.tableCell, styles.tableCellBlock]}>Total</Text>
<Text style={[styles.tableCell, styles.tableCellTime]} />
<Text style={[styles.tableCell, styles.tableCellTime]} />
<Text style={[styles.tableCell, styles.tableCellDuration]}>
{totalDuration}
</Text>
</View>
);
return (
<View style={styles.tableContainer}>
{renderHeader}
{renderEntries}
{renderFooter}
</View>
);
}

View file

@ -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 && (
<>
<Text style={[styles.tableCell, styles.tableCellTime]}>
{formatPdfRowDate(entry.startTime, settings)}
</Text>
<Text style={[styles.tableCell, styles.tableCellTime]}>
{formatPdfRowDate(entry.endTime ?? currentTime, settings)}
</Text>
</>
);
// Render the child rows for groups
const renderChildren = entry.subEntries != null && (
<View style={[styles.tableIndent, styles.tableRowWrapper]}>
{entry.subEntries.map((entry, index) => (
<TimesheetPdfTableRow
entry={entry}
key={index}
currentTime={currentTime}
settings={settings}
styles={styles}
/>
))}
</View>
);
return (
<Fragment>
<View style={styles.tableRow} wrap={false}>
<Text style={[styles.tableCell, styles.tableCellBlock]}>
{entry.name}
</Text>
{renderTiming}
<Text style={[styles.tableCell, styles.tableCellTime]}>
{durationFormatted}
</Text>
</View>
{renderChildren}
</Fragment>
);
}

View file

@ -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<NodeJS.ReadableStream> {
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);
}

View file

@ -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<NodeJS.ReadableStream> {
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],
},
];
}

View file

@ -1,119 +0,0 @@
import { StyleSheet } from "@react-pdf/renderer";
export type Styles = ReturnType<typeof createStyles>;
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",
},
});
}

View file

@ -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(

View file

@ -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(

View file

@ -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));
});

View file

@ -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;

View file

@ -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(

View file

@ -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);
});

File diff suppressed because one or more lines are too long