diff --git a/.gitignore b/.gitignore index 81593582..fb54d74c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ index.html main.js node_modules/ src/Main/index.d.ts +src/Worker/index.d.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 93524e65..296d4b0a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,11 +7,13 @@ To get up and running: ``` npm install # install dependencies -npm run generate # generate ts type definitions for elm ports +npm run generate # generate ts type definitions for the main app +npm run generate-worker # generate ts type definitions for the worker app npm run dev # run dev build ``` ## Release Checklist +- run ts generators - elm-review - elm-test - elm-coverage diff --git a/README.md b/README.md index 0decb2f0..808bdc21 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,9 @@ An [Obsidian](https://obsidian.md/) plugin to make working with tasks a pleasure - Tag based (use `#tags` to define columns). ## New -- Due dates can be edited on the board - right click on a card - to bring up the context menu. See the [Editing due date section](#editing-due-date). - - +- No new features: I have split out some of the internal workings in preparation + for drag and drop - hopefully managed this without introducing any bugs or + performance issues. ## Installation Please install via the regular Community Plugins setting tab within Obsidian. diff --git a/TODO.md b/TODO.md index f474708d..c4ad5b39 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,45 @@ +- column sorting + - title, due date, path + - do I want to introduce priority at this point? + +STEP 3 +------ +- V: maintains (via W) file(s) to hold the order of cards + W needs to maintain the files as it needs keeping up to date + irrespcetive of whether the view is active + V needs to read the file at startup so it puts cards in the right places + if user DnDs then V sends postion changes to W which writes them to the file + hmmmmmmm + + +- compare somne different forms of taskItem serialisation + - basic TsEncode/Decode + - can I make the taskItem encoding a bit more compact -> specially encoding Union types! + - original strings and parse to decode +- TaskItemFields -> make alphabetical (contents is at the end atm) +- perhaps when I am removing taskItems from the view I could request a reload of all if + the taskItem I am deleting doesn't exist +- howabout when adding taskItems communicating the expected total and if this doesn't match + what the view has it can request a reload. +- do I need the same states in the view now that I am not loading markdown? +- typescipt tidying -> am I using async only where I need to? + +- can I make it so I load any settings at startup but save the latest version + when I get them back from elm? +- do I want to deal with Settings (as in ensure they are the latest version) in the worker? +- bug: if a file is deleted and it includes something which is shown in the completed + column then a blank item is added at the end as I do not render the markdown for + the newly added card. +- I get an error in the js console if I click on the command icon when the + cardboard view is visible + --- - touch events - iPad ?? - show errors on settings pane ?? # Cleanups +- do I need TaskItem.originalLine as it's the first line of originalBlock + would need a bit of work as there is no originalBlock for subtasks atm - simplify parsing as per typing tutor - replace regex stuff in TaskItem.toToggledSting with some form of token parsing - if something is on a board because of a subtask tag and that line also has a due date on it, should I use @@ -62,6 +99,7 @@ - undo buffer - for toggling completion - for deletion + - see: https://erkal.github.io/UndoRedo/index.html - could/should I use some taskpaper tags: @defer(date) - defer until date, e.g. 2016-04-19 5pm or next Thursday -3d @estimate(time span) - time estimate, e.g. 2h for 2 hours or 3w for 3 weeks. diff --git a/elm.json b/elm.json index 0c5ef200..e8225aea 100644 --- a/elm.json +++ b/elm.json @@ -25,6 +25,7 @@ "justinmimbs/time-extra": "1.1.1", "mpizenberg/elm-pointer-events": "5.0.0", "pzp1997/assoc-list": "1.0.0", + "rluiten/stringdistance": "1.0.4", "robinheghan/fnv1a": "1.0.0", "rtfeldman/elm-iso8601-date-strings": "1.1.4", "tripokey/elm-fuzzy": "5.2.1" @@ -42,12 +43,7 @@ "elm-explorations/test": "2.1.2" }, "indirect": { - "elm/project-metadata-utils": "1.0.2", - "elm/random": "1.0.0", - "miniBill/elm-unicode": "1.0.3", - "rtfeldman/elm-hex": "1.0.0", - "stil4m/elm-syntax": "7.3.2", - "stil4m/structured-writer": "1.0.3" + "elm/random": "1.0.0" } } } diff --git a/package-lock.json b/package-lock.json index 80d0d399..0c922bb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cardboard", - "version": "0.7.5", + "version": "0.7.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cardboard", - "version": "0.7.5", + "version": "0.7.8", "license": "MIT", "dependencies": { "moment": "^2.29.4" @@ -24,7 +24,7 @@ "elm-ts-interop": "^0.0.8", "elm-webpack-loader": "^8.0.0", "jest": "^29.7.0", - "obsidian": "^1.4.11", + "obsidian": "^1.5.7", "obsidian-daily-notes-interface": "^0.9.4", "ts-jest": "^29.1.1", "ts-loader": "^9.5.0", @@ -32,7 +32,8 @@ "tslib": "^2.6.2", "typescript": "^5.2.2", "webpack": "^5.89.0", - "webpack-cli": "^5.1.4" + "webpack-cli": "^5.1.4", + "webpack-merge": "^5.10.0" } }, "node_modules/@ampproject/remapping": { @@ -207,9 +208,9 @@ } }, "node_modules/@babel/core": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", - "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -217,11 +218,11 @@ "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.7", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -236,15 +237,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", @@ -260,16 +252,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", @@ -286,30 +268,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", @@ -436,14 +394,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", - "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6" + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" }, "engines": { "node": ">=6.9.0" @@ -535,9 +493,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -724,23 +682,23 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", - "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.23.5", @@ -749,8 +707,8 @@ "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -759,9 +717,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", @@ -779,21 +737,21 @@ "dev": true }, "node_modules/@codemirror/state": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz", - "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", + "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", "dev": true, "peer": true }, "node_modules/@codemirror/view": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.1.tgz", - "integrity": "sha512-kYtS+uqYw/q/0ytYxpkqE1JVuK5NsbmBklWYhwLFTKO9gVuTdh/kDEeZPKorbqHcJ+P+ucrhcsS1czVweOpT2g==", + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.24.1.tgz", + "integrity": "sha512-sBfP4rniPBRQzNakwuQEqjEuiJDWJyF2kqLLqij4WXRoVwPPJfjx966Eq3F7+OPQxDtMt/Q9MWLoZLWjeveBlg==", "dev": true, "peer": true, "dependencies": { - "@codemirror/state": "^6.1.4", - "style-mod": "^4.0.0", + "@codemirror/state": "^6.4.0", + "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, @@ -809,6 +767,16 @@ "node": ">=12" } }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -1170,46 +1138,6 @@ } } }, - "node_modules/@jest/reporters/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jest/reporters/node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -1236,16 +1164,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/source-map/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@jest/test-result": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", @@ -1302,16 +1220,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", @@ -1330,9 +1238,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.4.tgz", + "integrity": "sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==", "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", @@ -1344,9 +1252,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" @@ -1372,19 +1280,19 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.23.tgz", + "integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@pkgjs/parseargs": { @@ -1416,9 +1324,9 @@ } }, "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "dependencies": { "type-detect": "4.0.8" @@ -1464,9 +1372,9 @@ "dev": true }, "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, "node_modules/@types/babel__core": { @@ -1532,9 +1440,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.4.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", - "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", + "version": "8.56.3", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.3.tgz", + "integrity": "sha512-PvSf1wfv2wJpVIFUMSb+i4PvqNYkB9Rkp9ZDO3oaWzq4SKhsQk4mrMBr3ZH06I0hKrVGLBacmgl8JM4WVjb9dg==", "dev": true, "dependencies": { "@types/estree": "*", @@ -1542,9 +1450,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "dependencies": { "@types/eslint": "*", @@ -1552,9 +1460,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/graceful-fs": { @@ -1567,39 +1475,39 @@ } }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, "dependencies": { "@types/istanbul-lib-report": "*" } }, "node_modules/@types/jest": { - "version": "29.5.8", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz", - "integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==", + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -1607,9 +1515,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/keyv": { @@ -1622,33 +1530,33 @@ } }, "node_modules/@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "version": "20.11.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz", + "integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, "node_modules/@types/tern": { - "version": "0.23.7", - "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.7.tgz", - "integrity": "sha512-0YS9XCZ0LAhlP11HV9SqncUYyz9Ggsgc7Om/AmchKvoeFyj0qPaJmX6rJ93mJVExizWDzUMb49gAtVpI1uHd8Q==", + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", "dev": true, "dependencies": { "@types/estree": "*" @@ -1666,18 +1574,18 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.18", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.18.tgz", - "integrity": "sha512-eIJR1UER6ur3EpKM3d+2Pgd+ET+k6Kn9B4ZItX0oPjjVI5PrfaRjKyLT5UYendDpLuoiJMNJvovLQbEXqhsPaw==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, "node_modules/@webassemblyjs/ast": { @@ -1883,9 +1791,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1904,9 +1812,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -2066,15 +1974,6 @@ "node": ">=8" } }, - "node_modules/babel-plugin-istanbul/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/babel-plugin-jest-hoist": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", @@ -2207,9 +2106,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -2226,8 +2125,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -2299,9 +2198,9 @@ } }, "node_modules/cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", "dev": true, "dependencies": { "clone-response": "^1.0.2", @@ -2335,9 +2234,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001572", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001572.tgz", - "integrity": "sha512-1Pbh5FLmn5y4+QhNyJE9j3/7dK44dGB83/ZMjv/qJk86TvDbjk0LosiZo0i0WB0Vx607qMX9jYrn1VLHCkN4rw==", + "version": "1.0.30001589", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz", + "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==", "dev": true, "funding": [ { @@ -2380,16 +2279,10 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -2402,6 +2295,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -2434,9 +2330,9 @@ } }, "node_modules/ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, "funding": [ { @@ -2467,9 +2363,9 @@ } }, "node_modules/cli-spinners": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "engines": { "node": ">=6" @@ -2650,15 +2546,15 @@ "dev": true }, "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, "node_modules/commander": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", - "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, "engines": { "node": "^12.20.0 || >=14" @@ -2848,9 +2744,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.617", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.617.tgz", - "integrity": "sha512-sYNE3QxcDS4ANW1k4S/wWYMXjCVcFSOX3Bg8jpuMFaXt/x8JCmp0R1Xe1ZXDX4WXnSRBf+GJ/3eGWicUuQq5cg==", + "version": "1.4.681", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.681.tgz", + "integrity": "sha512-1PpuqJUFWoXZ1E54m8bsLPVYwIVCRzvaL+n5cjigGga4z854abDnFRc+cTa2th4S79kyGqya/1xoR7h+Y5G5lg==", "dev": true }, "node_modules/elm": { @@ -2944,70 +2840,6 @@ "url": "https://github.com/sponsors/jfmengels" } }, - "node_modules/elm-review/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/elm-review/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/elm-review/node_modules/rimraf": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", - "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", - "dev": true, - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/elm-review/node_modules/rimraf/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/elm-solve-deps-wasm": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/elm-solve-deps-wasm/-/elm-solve-deps-wasm-1.0.2.tgz", @@ -3048,9 +2880,9 @@ } }, "node_modules/elm-test/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -3067,9 +2899,9 @@ } }, "node_modules/elm-test/node_modules/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -3191,9 +3023,9 @@ } }, "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz", + "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==", "dev": true, "bin": { "envinfo": "dist/cli.js" @@ -3218,9 +3050,9 @@ "dev": true }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -3440,6 +3272,15 @@ "node": ">=6.4.0" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, "node_modules/folder-hash": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/folder-hash/-/folder-hash-3.3.3.tgz", @@ -3507,9 +3348,9 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "optional": true, @@ -3521,10 +3362,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -3658,18 +3502,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3679,6 +3511,18 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3805,12 +3649,12 @@ } }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3934,14 +3778,14 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", - "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", "dev": true, "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" }, @@ -3949,38 +3793,71 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "yallist": "^4.0.0" }, "engines": { "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "lru-cache": "^6.0.0" }, - "engines": { + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { "node": ">=10" } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -4048,21 +3925,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-changed-files/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-circus": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", @@ -4094,21 +3956,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", @@ -4200,15 +4047,6 @@ "node": ">=12" } }, - "node_modules/jest-cli/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/jest-config": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", @@ -4348,36 +4186,6 @@ "fsevents": "^2.3.2" } }, - "node_modules/jest-haste-map/node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/jest-leak-detector": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", @@ -4531,61 +4339,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/jest-runtime": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", @@ -4650,6 +4403,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -4716,17 +4502,18 @@ } }, "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "dependencies": { "@types/node": "*", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, "engines": { - "node": ">= 10.13.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -4818,9 +4605,9 @@ } }, "node_modules/keyv": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", - "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { "json-buffer": "3.0.1" @@ -4944,15 +4731,12 @@ } }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "yallist": "^3.0.2" } }, "node_modules/make-dir": { @@ -4970,6 +4754,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5056,9 +4873,9 @@ } }, "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5073,10 +4890,22 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "engines": { "node": "*" } @@ -5233,8 +5062,8 @@ } }, "node_modules/obsidian": { - "version": "1.4.11", - "resolved": "git+ssh://git@github.com/obsidianmd/obsidian-api.git#791214a68d0dc322b88e5abce617bdf603cc2a2d", + "version": "1.5.7", + "resolved": "git+ssh://git@github.com/obsidianmd/obsidian-api.git#9754fc87dd9a417f198a5cad7f08959206d2f69c", "dev": true, "license": "MIT", "dependencies": { @@ -5265,6 +5094,15 @@ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "dev": true }, + "node_modules/obsidian/node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5322,15 +5160,15 @@ } }, "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5348,6 +5186,21 @@ "node": ">=8" } }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -5425,13 +5278,10 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.2.tgz", - "integrity": "sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, - "dependencies": { - "semver": "^7.3.5" - }, "engines": { "node": "14 || >=16.14" } @@ -5525,9 +5375,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -5577,9 +5427,9 @@ "dev": true }, "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -5630,12 +5480,12 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -5708,15 +5558,67 @@ } }, "node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", "dev": true, "dependencies": { - "glob": "^7.1.3" + "glob": "^10.3.7" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/safe-buffer": { @@ -5758,24 +5660,18 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -5851,9 +5747,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "dependencies": { "buffer-from": "^1.0.0", @@ -5997,9 +5893,9 @@ } }, "node_modules/style-mod": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", - "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz", + "integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==", "dev": true, "peer": true }, @@ -6062,16 +5958,16 @@ "node": ">=6.0.0" } }, - "node_modules/temp/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "dependencies": { - "minimist": "^1.2.6" + "glob": "^7.1.3" }, "bin": { - "mkdirp": "bin/cmd.js" + "rimraf": "bin.js" } }, "node_modules/terminal-link": { @@ -6091,9 +5987,9 @@ } }, "node_modules/terser": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", - "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", + "version": "5.28.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.28.1.tgz", + "integrity": "sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -6109,16 +6005,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -6142,14 +6038,33 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/terser/node_modules/commander": { @@ -6158,6 +6073,16 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -6206,9 +6131,9 @@ } }, "node_modules/ts-jest": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", - "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", "dev": true, "dependencies": { "bs-logger": "0.x", @@ -6224,7 +6149,7 @@ "ts-jest": "cli.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", @@ -6248,19 +6173,43 @@ } } }, - "node_modules/ts-jest/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "node_modules/ts-jest/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, + "node_modules/ts-jest/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/ts-loader": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", - "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -6277,6 +6226,33 @@ "webpack": "^5.0.0" } }, + "node_modules/ts-loader/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-loader/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ts-loader/node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -6286,10 +6262,16 @@ "node": ">= 8" } }, + "node_modules/ts-loader/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -6357,9 +6339,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6376,9 +6358,9 @@ "dev": true }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" @@ -6449,20 +6431,10 @@ "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/w3c-keyname": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", - "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "dev": true, "peer": true }, @@ -6498,19 +6470,19 @@ } }, "node_modules/webpack": { - "version": "5.89.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", - "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "version": "5.90.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", + "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", + "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.11.5", "@webassemblyjs/wasm-edit": "^1.11.5", "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", @@ -6524,7 +6496,7 @@ "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", + "terser-webpack-plugin": "^5.3.10", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -6599,12 +6571,13 @@ } }, "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dev": true, "dependencies": { "clone-deep": "^4.0.1", + "flat": "^5.0.2", "wildcard": "^2.0.0" }, "engines": { @@ -6636,15 +6609,15 @@ } }, "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true }, "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, "node_modules/wrap-ansi": { @@ -6714,9 +6687,9 @@ "dev": true }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, "node_modules/yargs": { @@ -6738,13 +6711,12 @@ } }, "node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "engines": { + "node": ">=12" } }, "node_modules/yargs/node_modules/ansi-regex": { @@ -6796,6 +6768,21 @@ "node": ">=6" } }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yargs/node_modules/p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -6843,6 +6830,16 @@ "node": ">=6" } }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 575fb761..782e99b4 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "description": "Card based view for your obsidian Tasks", "main": "main.js", "scripts": { - "build": "webpack --progress --color --mode=production", - "dev": "webpack --mode=development --watch --progress --color", + "build": "webpack --progress --color --config webpack.prod.js", + "dev": "webpack --config webpack.dev.js --watch --progress --color", "generate": "elm-ts-interop --definition-module InteropDefinitions --output src/Main/index.d.ts", + "generate-worker": "elm-ts-interop --definition-module Worker.InteropDefinitions --entrypoint Worker --output src/Worker/index.d.ts", "test": "elm-test tests", "test-ts": "jest" }, @@ -26,7 +27,7 @@ "elm-ts-interop": "^0.0.8", "elm-webpack-loader": "^8.0.0", "jest": "^29.7.0", - "obsidian": "^1.4.11", + "obsidian": "^1.5.7", "obsidian-daily-notes-interface": "^0.9.4", "ts-jest": "^29.1.1", "ts-loader": "^9.5.0", @@ -34,7 +35,8 @@ "tslib": "^2.6.2", "typescript": "^5.2.2", "webpack": "^5.89.0", - "webpack-cli": "^5.1.4" + "webpack-cli": "^5.1.4", + "webpack-merge": "^5.10.0" }, "dependencies": { "moment": "^2.29.4" diff --git a/src/DecodeHelpers.elm b/src/DecodeHelpers.elm index e1caf2a6..582ed550 100644 --- a/src/DecodeHelpers.elm +++ b/src/DecodeHelpers.elm @@ -1,10 +1,25 @@ -module DecodeHelpers exposing (toElmVariant) +module DecodeHelpers exposing + ( dateDecoder + , toElmVariant + , toElmVariant0 + ) +import Date exposing (Date) import Json.Encode as JE import TsJson.Decode as TsDecode +dateDecoder : TsDecode.Decoder Date +dateDecoder = + TsDecode.map Date.fromRataDie TsDecode.int + + toElmVariant : String -> (value -> a) -> TsDecode.Decoder value -> TsDecode.Decoder a toElmVariant tagName constructor decoder_ = TsDecode.field "tag" (TsDecode.literal constructor (JE.string tagName)) |> TsDecode.andMap (TsDecode.field "data" decoder_) + + +toElmVariant0 : String -> a -> TsDecode.Decoder a +toElmVariant0 tagName constructor = + TsDecode.field "tag" (TsDecode.literal constructor (JE.string tagName)) diff --git a/src/DueDate.elm b/src/DueDate.elm index 45f2ac76..a771f5cf 100644 --- a/src/DueDate.elm +++ b/src/DueDate.elm @@ -1,9 +1,61 @@ -module DueDate exposing (DueDate(..)) +module DueDate exposing + ( DueDate(..) + , decoder + , encoder + ) import Date exposing (Date) +import DecodeHelpers +import EncodeHelpers +import Json.Encode as JE +import TsJson.Decode as TsDecode +import TsJson.Encode as TsEncode type DueDate - = SetToDate Date + = NotSet + | SetToDate Date | SetToNone - | NotSet + + + +-- SERIALIZE + + +decoder : TsDecode.Decoder DueDate +decoder = + TsDecode.oneOf + [ DecodeHelpers.toElmVariant0 "NotSet" NotSet + , toElmSetToDate "SetToDate" SetToDate DecodeHelpers.dateDecoder + , DecodeHelpers.toElmVariant0 "SetToNone" SetToNone + ] + + +encoder : TsEncode.Encoder DueDate +encoder = + TsEncode.union + (\vNotSet vSetToDate vSetToNone value -> + case value of + NotSet -> + vNotSet + + SetToDate date -> + vSetToDate date + + SetToNone -> + vSetToNone + ) + |> TsEncode.variant0 "NotSet" + |> TsEncode.variantObject "SetToDate" [ TsEncode.required "date" identity EncodeHelpers.dateEncoder ] + |> TsEncode.variant0 "SetToNone" + |> TsEncode.buildUnion + + + +-- PRIVATE + + +toElmSetToDate : String -> (value -> a) -> TsDecode.Decoder value -> TsDecode.Decoder a +toElmSetToDate tagName constructor decoder_ = + TsDecode.field "tag" (TsDecode.literal constructor (JE.string tagName)) + |> TsDecode.andMap (TsDecode.field "date" decoder_) diff --git a/src/EncodeHelpers.elm b/src/EncodeHelpers.elm new file mode 100644 index 00000000..d1df6ae0 --- /dev/null +++ b/src/EncodeHelpers.elm @@ -0,0 +1,9 @@ +module EncodeHelpers exposing (dateEncoder) + +import Date exposing (Date) +import TsJson.Encode as TsEncode + + +dateEncoder : TsEncode.Encoder Date +dateEncoder = + TsEncode.map Date.toRataDie TsEncode.int diff --git a/src/InteropDefinitions.elm b/src/InteropDefinitions.elm index d422dd9f..abac6cbf 100644 --- a/src/InteropDefinitions.elm +++ b/src/InteropDefinitions.elm @@ -2,14 +2,7 @@ module InteropDefinitions exposing ( Flags , FromElm(..) , ToElm(..) - , addFilePreviewHoversEncoder - , deleteTaskEncoder - , displayTaskMarkdownEncoder , interop - , openTaskSourceFileEncoder - , showCardContextMenuEncoder - , trackDraggableEncoder - , updateTasksEncoder ) import DataviewTaskCompletion exposing (DataviewTaskCompletion) @@ -17,8 +10,9 @@ import DecodeHelpers import DragAndDrop.Coords as Coords exposing (Coords) import DragAndDrop.DragData as DragData exposing (DragData) import Filter exposing (Filter) -import MarkdownFile exposing (MarkdownFile) import Settings exposing (Settings) +import TaskItem exposing (TaskItem, TaskItemFields) +import TaskList exposing (TaskList) import TextDirection exposing (TextDirection) import TsJson.Decode as TsDecode import TsJson.Encode as TsEncode exposing (required) @@ -27,14 +21,15 @@ import TsJson.Encode as TsEncode exposing (required) type FromElm = AddFilePreviewHovers (List { filePath : String, id : String }) | CloseView - | DeleteTask { filePath : String, lineNumber : Int, originalText : String } + | DeleteTask TaskItemFields | DisplayTaskMarkdown (List { filePath : String, taskMarkdown : List { id : String, markdown : String } }) - | ElmInitialized - | OpenTaskSourceFile { filePath : String, lineNumber : Int, originalText : String } + | ElmInitialized String + | OpenTaskSourceFile TaskItemFields | RequestFilterCandidates | ShowCardContextMenu { clientPos : ( Float, Float ), cardId : String } | TrackDraggable { dragType : String, clientPos : Coords, draggableId : String } - | UpdateTasks { filePath : String, tasks : List { lineNumber : Int, originalText : String, newText : String } } + | UpdateSettings Settings + | UpdateTasks { filePath : String, tasks : List { lineNumber : Int, originalLine : String, newText : String } } type ToElm @@ -42,14 +37,14 @@ type ToElm | ConfigChanged TextDirection | EditCardDueDate String | ElementDragged DragData - | FileAdded MarkdownFile - | FileDeleted String - | FileRenamed ( String, String ) - | FileUpdated MarkdownFile | FilterCandidates (List Filter) - | AllMarkdownLoaded | SettingsUpdated Settings | ShowBoard Int + | TaskItemsAdded TaskList + | TaskItemsDeleted (List String) + | TaskItemsDeletedAndAdded ( List String, List TaskItem ) + | TaskItemsRefreshed TaskList + | TaskItemsUpdated (List ( String, TaskItem )) type alias Flags = @@ -72,73 +67,6 @@ interop = --- ENCODERS - - -addFilePreviewHoversEncoder : TsEncode.Encoder (List { filePath : String, id : String }) -addFilePreviewHoversEncoder = - TsEncode.list - (TsEncode.object - [ required "filePath" .filePath TsEncode.string - , required "id" .id TsEncode.string - ] - ) - - -deleteTaskEncoder : TsEncode.Encoder { a | filePath : String, lineNumber : Int, originalText : String } -deleteTaskEncoder = - TsEncode.object - [ required "filePath" .filePath TsEncode.string - , required "lineNumber" .lineNumber TsEncode.int - , required "originalText" .originalText TsEncode.string - ] - - -displayTaskMarkdownEncoder : TsEncode.Encoder (List { filePath : String, taskMarkdown : List { id : String, markdown : String } }) -displayTaskMarkdownEncoder = - TsEncode.list - (TsEncode.object - [ required "filePath" .filePath TsEncode.string - , required "taskMarkdown" .taskMarkdown (TsEncode.list markdownListEncoder) - ] - ) - - -openTaskSourceFileEncoder : TsEncode.Encoder { a | filePath : String, lineNumber : Int, originalText : String } -openTaskSourceFileEncoder = - TsEncode.object - [ required "filePath" .filePath TsEncode.string - , required "lineNumber" .lineNumber TsEncode.int - , required "originalText" .originalText TsEncode.string - ] - - -showCardContextMenuEncoder : TsEncode.Encoder { a | clientPos : ( Float, Float ), cardId : String } -showCardContextMenuEncoder = - TsEncode.object - [ required "clientPos" .clientPos (TsEncode.tuple TsEncode.float TsEncode.float) - , required "cardId" .cardId TsEncode.string - ] - - -trackDraggableEncoder : TsEncode.Encoder { dragType : String, clientPos : Coords, draggableId : String } -trackDraggableEncoder = - TsEncode.object - [ required "dragType" .dragType TsEncode.string - , required "clientPos" .clientPos Coords.encoder - , required "draggableId" .draggableId TsEncode.string - ] - - -updateTasksEncoder : TsEncode.Encoder { filePath : String, tasks : List { lineNumber : Int, originalText : String, newText : String } } -updateTasksEncoder = - TsEncode.object - [ required "filePath" .filePath TsEncode.string - , required "tasks" .tasks (TsEncode.list taskUpdatesEncoder) - ] - - - -- INTEROP @@ -161,21 +89,21 @@ toElm = , DecodeHelpers.toElmVariant "configChanged" ConfigChanged configChangedDecoder , DecodeHelpers.toElmVariant "editCardDueDate" EditCardDueDate TsDecode.string , DecodeHelpers.toElmVariant "elementDragged" ElementDragged DragData.decoder - , DecodeHelpers.toElmVariant "fileAdded" FileAdded MarkdownFile.decoder - , DecodeHelpers.toElmVariant "fileDeleted" FileDeleted TsDecode.string - , DecodeHelpers.toElmVariant "fileRenamed" FileRenamed renamedFileDecoder - , DecodeHelpers.toElmVariant "fileUpdated" FileUpdated MarkdownFile.decoder , DecodeHelpers.toElmVariant "filterCandidates" FilterCandidates (TsDecode.list Filter.decoder) - , DecodeHelpers.toElmVariant "allMarkdownLoaded" (always AllMarkdownLoaded) (TsDecode.succeed ()) , DecodeHelpers.toElmVariant "settingsUpdated" SettingsUpdated Settings.decoder , DecodeHelpers.toElmVariant "showBoard" ShowBoard TsDecode.int + , DecodeHelpers.toElmVariant "taskItemsAdded" TaskItemsAdded TaskList.decoder + , DecodeHelpers.toElmVariant "taskItemsDeleted" TaskItemsDeleted (TsDecode.list TsDecode.string) + , DecodeHelpers.toElmVariant "taskItemsDeletedAndAdded" TaskItemsDeletedAndAdded deleteAndAddDecoder + , DecodeHelpers.toElmVariant "taskItemsRefreshed" TaskItemsRefreshed TaskList.decoder + , DecodeHelpers.toElmVariant "taskItemsUpdated" TaskItemsUpdated updateDetailsDecoder ] fromElm : TsEncode.Encoder FromElm fromElm = TsEncode.union - (\vAddFilePreviewHovers vCloseView vDeleteTask vDisplayTaskMarkdown vElmInitialized vOpenTaskSourceFile vRequestPaths vShowCardContextMenu vTrackDraggable _ vUpdateTasks value -> + (\vAddFilePreviewHovers vCloseView vDeleteTask vDisplayTaskMarkdown vElmInitialized vOpenTaskSourceFile vRequestPaths vShowCardContextMenu vTrackDraggable vUpdateSettings vUpdateTasks value -> case value of AddFilePreviewHovers info -> vAddFilePreviewHovers info @@ -183,17 +111,17 @@ fromElm = CloseView -> vCloseView - DeleteTask info -> - vDeleteTask info + DeleteTask taskItemFields -> + vDeleteTask taskItemFields DisplayTaskMarkdown info -> vDisplayTaskMarkdown info - ElmInitialized -> - vElmInitialized + ElmInitialized uniqueId -> + vElmInitialized uniqueId - OpenTaskSourceFile info -> - vOpenTaskSourceFile info + OpenTaskSourceFile taskItemFields -> + vOpenTaskSourceFile taskItemFields RequestFilterCandidates -> vRequestPaths @@ -204,6 +132,9 @@ fromElm = TrackDraggable info -> vTrackDraggable info + UpdateSettings settings -> + vUpdateSettings settings + UpdateTasks info -> vUpdateTasks info ) @@ -211,7 +142,7 @@ fromElm = |> TsEncode.variant0 "closeView" |> TsEncode.variantTagged "deleteTask" deleteTaskEncoder |> TsEncode.variantTagged "displayTaskMarkdown" displayTaskMarkdownEncoder - |> TsEncode.variant0 "elmInitialized" + |> TsEncode.variantTagged "elmInitialized" TsEncode.string |> TsEncode.variantTagged "openTaskSourceFile" openTaskSourceFileEncoder |> TsEncode.variant0 "requestFilterCandidates" |> TsEncode.variantTagged "showCardContextMenu" showCardContextMenuEncoder @@ -225,12 +156,46 @@ fromElm = -- HELPERS +addFilePreviewHoversEncoder : TsEncode.Encoder (List { filePath : String, id : String }) +addFilePreviewHoversEncoder = + TsEncode.list + (TsEncode.object + [ required "filePath" .filePath TsEncode.string + , required "id" .id TsEncode.string + ] + ) + + configChangedDecoder : TsDecode.Decoder TextDirection configChangedDecoder = TsDecode.succeed TextDirection.fromRtlFlag |> TsDecode.andMap (TsDecode.field "rightToLeft" TsDecode.bool) +deleteAndAddDecoder : TsDecode.Decoder ( List String, List TaskItem ) +deleteAndAddDecoder = + TsDecode.tuple (TsDecode.list TsDecode.string) (TsDecode.list TaskItem.decoder) + + +deleteTaskEncoder : TsEncode.Encoder { a | filePath : String, lineNumber : Int, originalLine : String } +deleteTaskEncoder = + TsEncode.object + [ required "filePath" .filePath TsEncode.string + , required "lineNumber" .lineNumber TsEncode.int + , required "originalLine" .originalLine TsEncode.string + ] + + +displayTaskMarkdownEncoder : TsEncode.Encoder (List { filePath : String, taskMarkdown : List { id : String, markdown : String } }) +displayTaskMarkdownEncoder = + TsEncode.list + (TsEncode.object + [ required "filePath" .filePath TsEncode.string + , required "taskMarkdown" .taskMarkdown (TsEncode.list markdownListEncoder) + ] + ) + + markdownListEncoder : TsEncode.Encoder { id : String, markdown : String } markdownListEncoder = TsEncode.object @@ -239,17 +204,49 @@ markdownListEncoder = ] -renamedFileDecoder : TsDecode.Decoder ( String, String ) -renamedFileDecoder = - TsDecode.succeed Tuple.pair - |> TsDecode.andMap (TsDecode.field "oldPath" TsDecode.string) - |> TsDecode.andMap (TsDecode.field "newPath" TsDecode.string) +openTaskSourceFileEncoder : TsEncode.Encoder { a | filePath : String, lineNumber : Int, originalLine : String } +openTaskSourceFileEncoder = + TsEncode.object + [ required "filePath" .filePath TsEncode.string + , required "lineNumber" .lineNumber TsEncode.int + , required "originalLine" .originalLine TsEncode.string + ] -taskUpdatesEncoder : TsEncode.Encoder { lineNumber : Int, originalText : String, newText : String } +showCardContextMenuEncoder : TsEncode.Encoder { a | clientPos : ( Float, Float ), cardId : String } +showCardContextMenuEncoder = + TsEncode.object + [ required "clientPos" .clientPos (TsEncode.tuple TsEncode.float TsEncode.float) + , required "cardId" .cardId TsEncode.string + ] + + +taskUpdatesEncoder : TsEncode.Encoder { lineNumber : Int, originalLine : String, newText : String } taskUpdatesEncoder = TsEncode.object [ required "lineNumber" .lineNumber TsEncode.int - , required "originalText" .originalText TsEncode.string + , required "originalLine" .originalLine TsEncode.string , required "newText" .newText TsEncode.string ] + + +updateDetailsDecoder : TsDecode.Decoder (List ( String, TaskItem )) +updateDetailsDecoder = + TsDecode.list <| TsDecode.tuple TsDecode.string TaskItem.decoder + + +trackDraggableEncoder : TsEncode.Encoder { dragType : String, clientPos : Coords, draggableId : String } +trackDraggableEncoder = + TsEncode.object + [ required "dragType" .dragType TsEncode.string + , required "clientPos" .clientPos Coords.encoder + , required "draggableId" .draggableId TsEncode.string + ] + + +updateTasksEncoder : TsEncode.Encoder { filePath : String, tasks : List { lineNumber : Int, originalLine : String, newText : String } } +updateTasksEncoder = + TsEncode.object + [ required "filePath" .filePath TsEncode.string + , required "tasks" .tasks (TsEncode.list taskUpdatesEncoder) + ] diff --git a/src/InteropPorts.elm b/src/InteropPorts.elm index 34a8b434..9380154b 100644 --- a/src/InteropPorts.elm +++ b/src/InteropPorts.elm @@ -20,11 +20,27 @@ import InteropDefinitions import Json.Decode import Json.Encode import Settings exposing (Settings) +import TaskItem exposing (TaskItemFields) import TsJson.Decode as TsDecode import TsJson.Encode as TsEncode import UpdatedTaskItem exposing (UpdatedTaskItem) + +-- FLAGS + + +decodeFlags : Json.Decode.Value -> Result Json.Decode.Error InteropDefinitions.Flags +decodeFlags flags = + Json.Decode.decodeValue + (InteropDefinitions.interop.flags |> TsDecode.decoder) + flags + + + +-- SUBSCRIPTIONS + + toElm : Sub (Result Json.Decode.Error InteropDefinitions.ToElm) toElm = (InteropDefinitions.interop.toElm |> TsDecode.decoder) @@ -40,20 +56,23 @@ addHoverToCardEditButtons : List Card -> Cmd msg addHoverToCardEditButtons cards = cards |> List.map (\c -> { filePath = Card.filePath c, id = Card.editButtonId c }) - |> encodeVariant "addFilePreviewHovers" InteropDefinitions.addFilePreviewHoversEncoder + |> InteropDefinitions.AddFilePreviewHovers + |> TsEncode.encoder InteropDefinitions.interop.fromElm |> interopFromElm closeView : Cmd msg closeView = - encodeVariant "closeView" (TsEncode.object []) () + InteropDefinitions.CloseView + |> TsEncode.encoder InteropDefinitions.interop.fromElm |> interopFromElm -deleteTask : { a | filePath : String, lineNumber : Int, originalText : String } -> Cmd msg -deleteTask info = - info - |> encodeVariant "deleteTask" InteropDefinitions.deleteTaskEncoder +deleteTask : TaskItemFields -> Cmd msg +deleteTask taskItemFields = + taskItemFields + |> InteropDefinitions.DeleteTask + |> TsEncode.encoder InteropDefinitions.interop.fromElm |> interopFromElm @@ -61,88 +80,75 @@ displayTaskMarkdown : List Card -> Cmd msg displayTaskMarkdown cards = cards |> List.map (\c -> { filePath = Card.filePath c, taskMarkdown = Card.markdownWithIds c }) - |> encodeVariant "displayTaskMarkdown" InteropDefinitions.displayTaskMarkdownEncoder + |> InteropDefinitions.DisplayTaskMarkdown + |> TsEncode.encoder InteropDefinitions.interop.fromElm |> interopFromElm -elmInitialized : Cmd msg -elmInitialized = - encodeVariant "elmInitialized" (TsEncode.object []) () +elmInitialized : String -> Cmd msg +elmInitialized uniqueId = + uniqueId + |> InteropDefinitions.ElmInitialized + |> TsEncode.encoder InteropDefinitions.interop.fromElm |> interopFromElm -openTaskSourceFile : { a | filePath : String, lineNumber : Int, originalText : String } -> Cmd msg -openTaskSourceFile info = - info - |> encodeVariant "openTaskSourceFile" InteropDefinitions.openTaskSourceFileEncoder +openTaskSourceFile : TaskItemFields -> Cmd msg +openTaskSourceFile taskItemFields = + taskItemFields + |> InteropDefinitions.OpenTaskSourceFile + |> TsEncode.encoder InteropDefinitions.interop.fromElm |> interopFromElm requestFilterCandidates : Cmd msg requestFilterCandidates = - encodeVariant "requestFilterCandidates" (TsEncode.object []) () + InteropDefinitions.RequestFilterCandidates + |> TsEncode.encoder InteropDefinitions.interop.fromElm |> interopFromElm rewriteTasks : String -> List UpdatedTaskItem -> Cmd msg rewriteTasks filePath updatedTaskItems = let - rewriteDetails : UpdatedTaskItem -> { lineNumber : Int, originalText : String, newText : String } + rewriteDetails : UpdatedTaskItem -> { lineNumber : Int, originalLine : String, newText : String } rewriteDetails updatedTaskItem = { lineNumber = UpdatedTaskItem.lineNumber updatedTaskItem - , originalText = UpdatedTaskItem.originalText updatedTaskItem + , originalLine = UpdatedTaskItem.originalLine updatedTaskItem , newText = UpdatedTaskItem.toString updatedTaskItem } in { filePath = filePath, tasks = List.map rewriteDetails updatedTaskItems } - |> encodeVariant "updateTasks" InteropDefinitions.updateTasksEncoder + |> InteropDefinitions.UpdateTasks + |> TsEncode.encoder InteropDefinitions.interop.fromElm |> interopFromElm showCardContextMenu : ( Float, Float ) -> String -> Cmd msg showCardContextMenu clientPos cardId = { clientPos = clientPos, cardId = cardId } - |> encodeVariant "showCardContextMenu" InteropDefinitions.showCardContextMenuEncoder + |> InteropDefinitions.ShowCardContextMenu + |> TsEncode.encoder InteropDefinitions.interop.fromElm |> interopFromElm trackDraggable : String -> Coords -> String -> Cmd msg trackDraggable dragType clientPos draggableId = { dragType = dragType, clientPos = clientPos, draggableId = draggableId } - |> encodeVariant "trackDraggable" InteropDefinitions.trackDraggableEncoder + |> InteropDefinitions.TrackDraggable + |> TsEncode.encoder InteropDefinitions.interop.fromElm |> interopFromElm updateSettings : Settings -> Cmd msg updateSettings settings = { settings | version = Settings.currentVersion } - |> encodeVariant "updateSettings" Settings.encoder + |> InteropDefinitions.UpdateSettings + |> TsEncode.encoder InteropDefinitions.interop.fromElm |> interopFromElm --- HELPERS - - -encodeVariant : String -> TsEncode.Encoder arg1 -> arg1 -> Json.Encode.Value -encodeVariant variantName encoder_ arg1 = - arg1 - |> (TsEncode.object - [ TsEncode.required "tag" identity (TsEncode.literal (Json.Encode.string variantName)) - , TsEncode.required "data" identity encoder_ - ] - |> TsEncode.encoder - ) - - -decodeFlags : Json.Decode.Value -> Result Json.Decode.Error InteropDefinitions.Flags -decodeFlags flags = - Json.Decode.decodeValue - (InteropDefinitions.interop.flags |> TsDecode.decoder) - flags - - - -- PORTS diff --git a/src/Main.elm b/src/Main.elm index 66e7df48..eb3c538c 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -1,6 +1,5 @@ module Main exposing (Model, Msg, main) -import BoardConfig import Boards import Browser import Browser.Events as Browser @@ -12,7 +11,6 @@ import Html exposing (Html) import InteropDefinitions import InteropPorts import Json.Decode as JD -import MarkdownFile exposing (MarkdownFile) import Page import Page.Board as BoardPage import Page.Settings as SettingsPage @@ -20,7 +18,7 @@ import SafeZipper import Session exposing (Session) import Settings exposing (Settings) import Task -import TaskItem +import TaskItem exposing (TaskItem) import TaskList exposing (TaskList) import TextDirection exposing (TextDirection) import Time exposing (Posix) @@ -44,7 +42,7 @@ init flags = ( Settings (SettingsPage.init Session.default) , Cmd.batch [ Task.perform ReceiveTime <| Task.map2 Tuple.pair Time.here Time.now - , InteropPorts.elmInitialized + , InteropPorts.elmInitialized "" ] ) @@ -58,7 +56,7 @@ init flags = |> forceAddWhenNoBoards , Cmd.batch [ InteropPorts.updateSettings <| Session.settings session - , InteropPorts.elmInitialized + , InteropPorts.elmInitialized okFlags.uniqueId , Task.perform ReceiveTime <| Task.map2 Tuple.pair Time.here Time.now ] ) @@ -118,23 +116,23 @@ type KeyValue type Msg = ActiveStateUpdated Bool - | AllMarkdownLoaded | BadInputFromTypeScript | ConfigChanged TextDirection | EditCardDueDateRequested String | ElementDragged DragData - | SettingsUpdated Settings | FilterCandidatesReceived (List Filter) | GotBoardPageMsg BoardPage.Msg | GotSettingsPageMsg SettingsPage.Msg | KeyDown KeyValue | ReceiveTime ( Time.Zone, Posix ) + | SettingsUpdated Settings | ShowBoard Int | Tick Posix - | VaultFileAdded MarkdownFile - | VaultFileDeleted String - | VaultFileRenamed ( String, String ) - | VaultFileUpdated MarkdownFile + | TaskItemsAdded TaskList + | TaskItemsDeletedAndAdded ( List String, List TaskItem ) + | TaskItemsRefreshed TaskList + | TaskItemsDeleted (List String) + | TaskItemsUpdated (List ( String, TaskItem )) update : Msg -> Model -> ( Model, Cmd Msg ) @@ -145,14 +143,6 @@ update msg model = , Cmd.none ) - ( AllMarkdownLoaded, _ ) -> - ( mapSession Session.finishAdding model - , Cmd.batch - [ InteropPorts.displayTaskMarkdown <| Session.cards (toSession model) - , InteropPorts.addHoverToCardEditButtons <| Session.cards (toSession model) - ] - ) - ( BadInputFromTypeScript, _ ) -> ( model, Cmd.none ) @@ -289,71 +279,46 @@ update msg model = , cmd ) - ( VaultFileAdded markdownFile, _ ) -> - let - newTasks : TaskList - newTasks = - TaskList.fromMarkdown (Session.dataviewTaskCompletion <| toSession model) markdownFile - - newModel : Model - newModel = - mapSession (\s -> Session.addTaskList newTasks s) model - in - ( newModel - , cmdForTaskRedraws markdownFile.filePath (toSession newModel) + ( TaskItemsAdded taskList, _ ) -> + ( mapSession (Session.addTaskList taskList) model + , cmdForTaskRedraws taskList (toSession model) ) - ( VaultFileDeleted filePath, _ ) -> - ( mapSession (\s -> Session.deleteItemsFromFile filePath s) model + ( TaskItemsDeleted taskItems, _ ) -> + ( mapSession (Session.removeTaskItems taskItems) model , Cmd.none ) - ( VaultFileRenamed ( oldPath, newPath ), _ ) -> + ( TaskItemsDeletedAndAdded ( deleteIds, toAdd ), _ ) -> let - newModel : Model - newModel = - mapSession (Session.updatePath oldPath newPath) model + addList : TaskList + addList = + TaskList.fromList toAdd in - ( newModel - , Cmd.batch - [ cmdForTaskRedraws newPath (toSession newModel) - , cmdForFilterPathRename newPath (toSession newModel) - ] + ( model + |> mapSession (Session.removeTaskItems deleteIds) + |> mapSession (Session.addTaskList addList) + , cmdForTaskRedraws addList (toSession model) ) - ( VaultFileUpdated markdownFile, _ ) -> - let - newTaskItems : TaskList - newTaskItems = - TaskList.fromMarkdown (Session.dataviewTaskCompletion <| toSession model) markdownFile + ( TaskItemsRefreshed taskList, _ ) -> + ( mapSession (Session.replaceTaskList taskList) model + , Cmd.none + ) - newModel : Model - newModel = - mapSession (\s -> Session.replaceTaskItems markdownFile.filePath newTaskItems s) model + ( TaskItemsUpdated updateDetails, _ ) -> + let + tasksToRedraw : TaskList + tasksToRedraw = + updateDetails + |> List.map Tuple.second + |> TaskList.fromList in - ( newModel - , cmdForTaskRedraws markdownFile.filePath (toSession newModel) + ( mapSession (Session.replaceTaskItems updateDetails) model + , cmdForTaskRedraws tasksToRedraw (toSession model) ) -cmdForFilterPathRename : String -> Session -> Cmd msg -cmdForFilterPathRename newPath session = - let - anyUpdatedFilters : Bool - anyUpdatedFilters = - Session.boardConfigs session - |> SafeZipper.toList - |> List.concatMap BoardConfig.filters - |> List.filter (\f -> Filter.filterType f == "Files" || Filter.filterType f == "Paths") - |> List.any (\f -> Filter.value f == newPath) - in - if anyUpdatedFilters then - InteropPorts.updateSettings <| Session.settings session - - else - Cmd.none - - cmdForDateChange : Session -> Cmd Msg cmdForDateChange session = let @@ -379,8 +344,8 @@ cmdForDateChange session = ] -cmdForTaskRedraws : String -> Session -> Cmd Msg -cmdForTaskRedraws newPath session = +cmdForTaskRedraws : TaskList -> Session -> Cmd Msg +cmdForTaskRedraws taskList session = let today : Date today = @@ -389,8 +354,7 @@ cmdForTaskRedraws newPath session = cards : List Card cards = - Session.taskList session - |> TaskList.filter (\i -> TaskItem.filePath i == newPath) + taskList |> Boards.init (Session.uniqueId session) (Session.boardConfigs session) |> Boards.cards (Session.ignoreFileNameDates session) today in @@ -453,30 +417,30 @@ subscriptions _ = InteropDefinitions.ElementDragged dragData -> ElementDragged dragData - InteropDefinitions.FileAdded markdownFile -> - VaultFileAdded markdownFile - - InteropDefinitions.FileDeleted filePath -> - VaultFileDeleted filePath - - InteropDefinitions.FileRenamed oldAndNewPath -> - VaultFileRenamed oldAndNewPath - - InteropDefinitions.FileUpdated markdownFile -> - VaultFileUpdated markdownFile - InteropDefinitions.FilterCandidates filterCandidates -> FilterCandidatesReceived filterCandidates - InteropDefinitions.AllMarkdownLoaded -> - AllMarkdownLoaded - InteropDefinitions.SettingsUpdated newSettings -> SettingsUpdated newSettings InteropDefinitions.ShowBoard index -> ShowBoard index + InteropDefinitions.TaskItemsAdded taskItems -> + TaskItemsAdded taskItems + + InteropDefinitions.TaskItemsDeleted taskIds -> + TaskItemsDeleted taskIds + + InteropDefinitions.TaskItemsDeletedAndAdded ( toDelete, toAdd ) -> + TaskItemsDeletedAndAdded ( toDelete, toAdd ) + + InteropDefinitions.TaskItemsRefreshed taskItems -> + TaskItemsRefreshed taskItems + + InteropDefinitions.TaskItemsUpdated updateDetails -> + TaskItemsUpdated updateDetails + Err _ -> BadInputFromTypeScript ) diff --git a/src/Session.elm b/src/Session.elm index 26f53583..1d293b86 100644 --- a/src/Session.elm +++ b/src/Session.elm @@ -7,7 +7,6 @@ module Session exposing , cards , dataviewTaskCompletion , default - , deleteItemsFromFile , dragTracker , findCard , finishAdding @@ -21,7 +20,9 @@ module Session exposing , moveBoard , moveColumn , moveDragable + , removeTaskItems , replaceTaskItems + , replaceTaskList , settings , stopTrackingDragable , switchToBoardAt @@ -370,19 +371,6 @@ addTaskList list ((Session config) as session) = updateTaskListState (State.Loaded (TaskList.append currentList list)) session -deleteItemsFromFile : String -> Session -> Session -deleteItemsFromFile filePath ((Session config) as session) = - case config.taskList of - State.Waiting -> - session - - State.Loading currentList -> - updateTaskListState (State.Loading (TaskList.removeForFile filePath currentList)) session - - State.Loaded currentList -> - updateTaskListState (State.Loaded (TaskList.removeForFile filePath currentList)) session - - finishAdding : Session -> Session finishAdding ((Session config) as session) = case config.taskList of @@ -396,17 +384,43 @@ finishAdding ((Session config) as session) = session -replaceTaskItems : String -> TaskList -> Session -> Session -replaceTaskItems filePath updatedList ((Session config) as session) = +removeTaskItems : List String -> Session -> Session +removeTaskItems taskIds ((Session config) as session) = case config.taskList of State.Waiting -> session State.Loading currentList -> - updateTaskListState (State.Loading (TaskList.replaceForFile filePath updatedList currentList)) session + updateTaskListState (State.Loading (TaskList.filter (\i -> not <| List.member (TaskItem.id i) taskIds) currentList)) session State.Loaded currentList -> - updateTaskListState (State.Loaded (TaskList.replaceForFile filePath updatedList currentList)) session + updateTaskListState (State.Loaded (TaskList.filter (\i -> not <| List.member (TaskItem.id i) taskIds) currentList)) session + + +replaceTaskItems : List ( String, TaskItem ) -> Session -> Session +replaceTaskItems updateDetails ((Session config) as session) = + case config.taskList of + State.Waiting -> + session + + State.Loading currentList -> + updateTaskListState (State.Loading (TaskList.replaceTaskItems updateDetails currentList)) session + + State.Loaded currentList -> + updateTaskListState (State.Loading (TaskList.replaceTaskItems updateDetails currentList)) session + + +replaceTaskList : TaskList -> Session -> Session +replaceTaskList newList ((Session config) as session) = + case config.taskList of + State.Waiting -> + updateTaskListState (State.Loaded newList) session + + State.Loading _ -> + updateTaskListState (State.Loaded newList) session + + State.Loaded _ -> + updateTaskListState (State.Loaded newList) session updatePath : String -> String -> Session -> Session diff --git a/src/Tag.elm b/src/Tag.elm index 2aeada77..fd3c5ad9 100644 --- a/src/Tag.elm +++ b/src/Tag.elm @@ -1,6 +1,8 @@ module Tag exposing ( Tag , containsInvalidCharacters + , decoder + , encoder , equals , matches , parser @@ -10,6 +12,8 @@ module Tag exposing import Parser as P exposing ((|.), (|=), Parser) import ParserHelper +import TsJson.Decode as TsDecode +import TsJson.Encode as TsEncode import Unicode @@ -36,6 +40,20 @@ parser = +-- SERIALISE + + +decoder : TsDecode.Decoder Tag +decoder = + TsDecode.map Tag TsDecode.string + + +encoder : TsEncode.Encoder Tag +encoder = + TsEncode.map toString TsEncode.string + + + -- UTILITIES diff --git a/src/TagList.elm b/src/TagList.elm index 95ca4a54..41da119a 100644 --- a/src/TagList.elm +++ b/src/TagList.elm @@ -5,7 +5,9 @@ module TagList exposing , containsTagMatching , containsTagMatchingOneOf , containsTagOtherThanThese + , decoder , empty + , encoder , filter , fromList , isEmpty @@ -19,6 +21,8 @@ import List.Extra as LE import Parser import Set exposing (Set) import Tag exposing (Tag) +import TsJson.Decode as TsDecode +import TsJson.Encode as TsEncode @@ -62,6 +66,21 @@ cons tag (TagList tags) = +-- SERIALISE + + +decoder : TsDecode.Decoder TagList +decoder = + TsDecode.list Tag.decoder + |> TsDecode.map TagList + + +encoder : TsEncode.Encoder TagList +encoder = + TsEncode.map toList <| TsEncode.list Tag.encoder + + + -- COMBINE @@ -147,6 +166,11 @@ toStrings (TagList ts) = -- PRIVATE +toList : TagList -> List Tag +toList (TagList ts) = + ts + + toTags : TagList -> List Tag toTags (TagList ts) = ts diff --git a/src/TaskItem.elm b/src/TaskItem.elm index 62459563..5cc4c486 100644 --- a/src/TaskItem.elm +++ b/src/TaskItem.elm @@ -1,18 +1,22 @@ module TaskItem exposing ( AutoCompletion(..) + , CompareResult(..) , Completion(..) , Content , TaskItem , TaskItemFields , allSubtasksWithMatchingTagCompleted , asSingleTaskItems + , compare , completedPosix , completion , containsId + , decoder , descendantTasks , due , dueRataDie , dummy + , encoder , fields , filePath , hasNotes @@ -27,7 +31,8 @@ module TaskItem exposing , isFromFile , lineNumber , notes - , originalText + , originalBlock + , originalLine , parser , removeFileNameDate , removeMatchingTags @@ -44,7 +49,9 @@ module TaskItem exposing import DataviewDate import DataviewTaskCompletion exposing (DataviewTaskCompletion) import Date exposing (Date) +import DecodeHelpers import DueDate exposing (DueDate) +import EncodeHelpers import FNV1a import Filter exposing (Filter, Scope) import List.Extra as LE @@ -52,10 +59,13 @@ import Maybe.Extra as ME import ObsidianTasksDate import Parser as P exposing ((|.), (|=), Parser) import ParserHelper exposing (isSpaceOrTab, lineEndOrEnd) +import StringDistance import Tag exposing (Tag) import TagList exposing (TagList) import TaskPaperTag import Time +import TsJson.Decode as TsDecode +import TsJson.Encode as TsEncode @@ -65,15 +75,16 @@ import Time type alias TaskItemFields = { autoComplete : AutoCompletion , completion : Completion + , contents : List Content , dueFile : Maybe Date , dueTag : DueDate , filePath : String , lineNumber : Int , notes : String - , originalText : String + , originalBlock : String + , originalLine : String , tags : TagList , title : List String - , contents : List Content } @@ -106,6 +117,14 @@ type IndentedItem | Note String +type CompareResult + = Different + | Identical + | Moved + | MovedAndUpdated + | Updated + + dummy : TaskItem dummy = TaskItem defaultFields [] @@ -120,7 +139,8 @@ defaultFields = , filePath = "" , lineNumber = 0 , notes = "" - , originalText = "" + , originalBlock = "" + , originalLine = "" , tags = TagList.empty , title = [] , contents = [] @@ -128,6 +148,34 @@ defaultFields = +-- ENCODE / DECODE + + +decoder : TsDecode.Decoder TaskItem +decoder = + TsDecode.succeed TaskItem + |> TsDecode.andMap (TsDecode.field "fields" taskItemFieldsDecoder) + |> TsDecode.andMap (TsDecode.field "subFields" (TsDecode.list taskItemFieldsDecoder)) + + +encoder : TsEncode.Encoder TaskItem +encoder = + let + foo : TaskItem -> { fields : TaskItemFields, subFields : List TaskItemFields } + foo (TaskItem fields_ subFields_) = + { fields = fields_ + , subFields = subFields_ + } + in + TsEncode.map foo + (TsEncode.object + [ TsEncode.required "fields" .fields taskItemFieldsEncoder + , TsEncode.required "subFields" .subFields (TsEncode.list taskItemFieldsEncoder) + ] + ) + + + -- INFO @@ -315,9 +363,14 @@ notes = .notes << fields -originalText : TaskItem -> String -originalText = - .originalText << fields +originalBlock : TaskItem -> String +originalBlock = + .originalBlock << fields + + +originalLine : TaskItem -> String +originalLine = + .originalLine << fields tags : TaskItem -> TagList @@ -423,6 +476,68 @@ titleWithTags taskItem = +-- COMPARE + + +compare : TaskItem -> TaskItem -> CompareResult +compare other this = + let + otherTitle : String + otherTitle = + title other + + sameBlock : Bool + sameBlock = + originalBlock this == originalBlock other + + samePlace : Bool + samePlace = + id this == id other + + sameTitle : Bool + sameTitle = + thisTitle == otherTitle + + similarTitle : Float -> Bool + similarTitle threshold = + let + distance : Float + distance = + StringDistance.sift3Distance otherTitle thisTitle + + maxLength : Float + maxLength = + toFloat <| max (String.length otherTitle) (String.length thisTitle) + in + distance / maxLength < threshold + + thisTitle : String + thisTitle = + title this + in + if samePlace && sameBlock then + Identical + + else if samePlace && similarTitle 0.25 then + Updated + + else if samePlace && sameTitle then + Updated + + else if not samePlace && sameBlock then + Moved + + else if not samePlace && similarTitle 0.12 then + MovedAndUpdated + + else if not samePlace && sameTitle then + MovedAndUpdated + + else + Different + + + -- MODIFICATION @@ -482,23 +597,11 @@ updateFilePath oldPath newPath ((TaskItem fields_ subtasks_) as taskItem) = parser : DataviewTaskCompletion -> String -> Maybe String -> TagList -> Int -> Parser TaskItem parser dataviewTaskCompletion pathToFile fileDate frontMatterTags bodyOffset = - (P.succeed taskItemFieldsBuilder + P.succeed itemWithOriginalBlock |= P.getOffset - |= P.getCol - |= P.succeed pathToFile - |= P.succeed frontMatterTags - |= P.succeed bodyOffset - |= P.getRow - |= prefixParser - |. P.chompWhile isSpaceOrTab - |= fileDateParser fileDate - |= contentParser dataviewTaskCompletion + |= taskItemParser dataviewTaskCompletion pathToFile fileDate frontMatterTags bodyOffset |= P.getOffset - |. lineEndOrEnd |= P.getSource - ) - |> P.andThen rejectIfNoTitle - |> P.andThen (addAnySubtasksAndNotes dataviewTaskCompletion pathToFile fileDate frontMatterTags bodyOffset) @@ -620,6 +723,13 @@ indentedItemParser dataviewTaskCompletion pathToFile fileDate frontMatterTags bo ] +itemWithOriginalBlock : Int -> TaskItem -> Int -> String -> TaskItem +itemWithOriginalBlock startOffset (TaskItem fields_ subtasks_) endOffset source = + TaskItem + { fields_ | originalBlock = String.trimRight <| String.slice startOffset (endOffset - 0) source } + subtasks_ + + notesParser : Parser IndentedItem notesParser = P.succeed Note @@ -743,13 +853,34 @@ taskItemFieldsBuilder startOffset startColumn path frontMatterTags bodyOffset ro |> (\tif -> { tif | dueFile = dueFromFile }) |> (\tif -> { tif | filePath = path }) |> (\tif -> { tif | lineNumber = bodyOffset + row }) - |> (\tif -> { tif | originalText = sourceText }) + |> (\tif -> { tif | originalLine = sourceText }) |> (\tif -> { tif | tags = TagList.append tif.tags frontMatterTags }) |> (\tif -> { tif | title = List.reverse tif.title }) |> (\tif -> { tif | contents = List.reverse contents }) |> addCompletionTime +taskItemParser : DataviewTaskCompletion -> String -> Maybe String -> TagList -> Int -> Parser TaskItem +taskItemParser dataviewTaskCompletion pathToFile fileDate frontMatterTags bodyOffset = + (P.succeed taskItemFieldsBuilder + |= P.getOffset + |= P.getCol + |= P.succeed pathToFile + |= P.succeed frontMatterTags + |= P.succeed bodyOffset + |= P.getRow + |= prefixParser + |. P.chompWhile isSpaceOrTab + |= fileDateParser fileDate + |= contentParser dataviewTaskCompletion + |= P.getOffset + |. lineEndOrEnd + |= P.getSource + ) + |> P.andThen rejectIfNoTitle + |> P.andThen (addAnySubtasksAndNotes dataviewTaskCompletion pathToFile fileDate frontMatterTags bodyOffset) + + toggleCompletion : { a | time : Time.Posix } -> TaskItem -> TaskItem toggleCompletion timeWithZone (TaskItem fields_ subtasks_) = case fields_.completion of @@ -783,6 +914,103 @@ tokenParser dataviewTaskCompletion = -- PRIVATE +autoCompletionDecoder : TsDecode.Decoder AutoCompletion +autoCompletionDecoder = + TsDecode.oneOf + [ DecodeHelpers.toElmVariant0 "FalseSpecified" FalseSpecified + , DecodeHelpers.toElmVariant0 "NotSpecifed" NotSpecifed + , DecodeHelpers.toElmVariant0 "TrueSpecified" TrueSpecified + ] + + +autoCompletionEncoder : TsEncode.Encoder AutoCompletion +autoCompletionEncoder = + TsEncode.union + (\vFalseSpecified vNotSpecifed vTrueSpecified value -> + case value of + FalseSpecified -> + vFalseSpecified + + NotSpecifed -> + vNotSpecifed + + TrueSpecified -> + vTrueSpecified + ) + |> TsEncode.variant0 "FalseSpecified" + |> TsEncode.variant0 "NotSpecifed" + |> TsEncode.variant0 "TrueSpecified" + |> TsEncode.buildUnion + + +completionDecoder : TsDecode.Decoder Completion +completionDecoder = + TsDecode.oneOf + [ DecodeHelpers.toElmVariant0 "Completed" Completed + , DecodeHelpers.toElmVariant "CompletedAt" CompletedAt (TsDecode.map Time.millisToPosix TsDecode.int) + , DecodeHelpers.toElmVariant0 "Incomplete" Incomplete + ] + + +completionEncoder : TsEncode.Encoder Completion +completionEncoder = + TsEncode.union + (\vCompleted vCompletedAt vIncomplete value -> + case value of + Completed -> + vCompleted + + CompletedAt timeStamp -> + vCompletedAt timeStamp + + Incomplete -> + vIncomplete + ) + |> TsEncode.variant0 "Completed" + |> TsEncode.variantTagged "CompletedAt" (TsEncode.map Time.posixToMillis TsEncode.int) + |> TsEncode.variant0 "Incomplete" + |> TsEncode.buildUnion + + +contentDecoder : TsDecode.Decoder Content +contentDecoder = + TsDecode.oneOf + [ DecodeHelpers.toElmVariant "AutoCompleteTag" AutoCompleteTag autoCompletionDecoder + , DecodeHelpers.toElmVariant "CompletedTag" CompletedTag (TsDecode.map Time.millisToPosix TsDecode.int) + , DecodeHelpers.toElmVariant "DueTag" DueTag DueDate.decoder + , DecodeHelpers.toElmVariant "ObsidianTag" ObsidianTag Tag.decoder + , DecodeHelpers.toElmVariant "Word" Word TsDecode.string + ] + + +contentEncoder : TsEncode.Encoder Content +contentEncoder = + TsEncode.union + (\vAutoCompleteTag vCompletedTag vDueTag vObsidianTag vWord value -> + case value of + AutoCompleteTag autoCompletion -> + vAutoCompleteTag autoCompletion + + CompletedTag timeStamp -> + vCompletedTag timeStamp + + DueTag date -> + vDueTag date + + ObsidianTag tag -> + vObsidianTag tag + + Word word -> + vWord word + ) + |> TsEncode.variantTagged "AutoCompleteTag" autoCompletionEncoder + |> TsEncode.variantTagged "CompletedTag" (TsEncode.map Time.posixToMillis TsEncode.int) + |> TsEncode.variantTagged "DueTag" DueDate.encoder + |> TsEncode.variantTagged "ObsidianTag" Tag.encoder + |> TsEncode.variantTagged "Word" TsEncode.string + |> TsEncode.buildUnion + + descendantTaskHasThisTag : String -> TaskItem -> Bool descendantTaskHasThisTag tagToMatch = let @@ -807,6 +1035,41 @@ mapTags fn taskItem = mapFields (\fs -> { fs | tags = fn fs.tags }) taskItem +taskItemFieldsDecoder : TsDecode.Decoder TaskItemFields +taskItemFieldsDecoder = + TsDecode.succeed TaskItemFields + |> TsDecode.andMap (TsDecode.field "autoComplete" autoCompletionDecoder) + |> TsDecode.andMap (TsDecode.field "completion" completionDecoder) + |> TsDecode.andMap (TsDecode.field "contents" (TsDecode.list contentDecoder)) + |> TsDecode.andMap (TsDecode.field "dueFile" (TsDecode.maybe DecodeHelpers.dateDecoder)) + |> TsDecode.andMap (TsDecode.field "dueTag" DueDate.decoder) + |> TsDecode.andMap (TsDecode.field "filePath" TsDecode.string) + |> TsDecode.andMap (TsDecode.field "lineNumber" TsDecode.int) + |> TsDecode.andMap (TsDecode.field "notes" TsDecode.string) + |> TsDecode.andMap (TsDecode.field "originalBlock" TsDecode.string) + |> TsDecode.andMap (TsDecode.field "originalLine" TsDecode.string) + |> TsDecode.andMap (TsDecode.field "tags" TagList.decoder) + |> TsDecode.andMap (TsDecode.field "title" (TsDecode.list TsDecode.string)) + + +taskItemFieldsEncoder : TsEncode.Encoder TaskItemFields +taskItemFieldsEncoder = + TsEncode.object + [ TsEncode.required "autoComplete" .autoComplete autoCompletionEncoder + , TsEncode.required "completion" .completion completionEncoder + , TsEncode.required "contents" .contents (TsEncode.list contentEncoder) + , TsEncode.required "dueFile" .dueFile (TsEncode.maybe EncodeHelpers.dateEncoder) + , TsEncode.required "dueTag" .dueTag DueDate.encoder + , TsEncode.required "filePath" .filePath TsEncode.string + , TsEncode.required "lineNumber" .lineNumber TsEncode.int + , TsEncode.required "notes" .notes TsEncode.string + , TsEncode.required "originalBlock" .originalBlock TsEncode.string + , TsEncode.required "originalLine" .originalLine TsEncode.string + , TsEncode.required "tags" .tags TagList.encoder + , TsEncode.required "title" .title (TsEncode.list TsEncode.string) + ] + + topLevelTaskHasThisTag : String -> TaskItem -> Bool topLevelTaskHasThisTag tagToMatch = let diff --git a/src/TaskList.elm b/src/TaskList.elm index 6104f25a..78462526 100644 --- a/src/TaskList.elm +++ b/src/TaskList.elm @@ -1,32 +1,38 @@ module TaskList exposing ( TaskList + , TaskListDiff , add , append , concat , containsTask + , decoder , empty + , encoder , filter , foldl + , fromList , fromMarkdown , map - , parser - , removeForFile - , replaceForFile + , markdownDiffs + , replaceTaskItems , taskContainingId , taskFromId , taskIds , taskTitles - , tasks + , toList , topLevelTasks ) import DataviewTaskCompletion exposing (DataviewTaskCompletion) +import Dict exposing (Dict) import List.Extra as LE import MarkdownFile exposing (MarkdownFile) import Parser as P exposing (Parser) import ParserHelper exposing (anyLineParser) import TagList exposing (TagList) import TaskItem exposing (TaskItem) +import TsJson.Decode as TsDecode +import TsJson.Encode as TsEncode @@ -37,6 +43,12 @@ type TaskList = TaskList (List TaskItem) +type alias TaskListDiff = + { toAdd : List TaskItem + , toDelete : List TaskItem + } + + -- CREATE @@ -46,6 +58,11 @@ empty = TaskList [] +fromList : List TaskItem -> TaskList +fromList = + TaskList + + fromMarkdown : DataviewTaskCompletion -> MarkdownFile -> TaskList fromMarkdown dataviewTaskCompletion markdownFile = P.run @@ -66,13 +83,17 @@ add item (TaskList list) = --- PARSE +-- SERIALIZE -parser : DataviewTaskCompletion -> String -> Maybe String -> TagList -> Int -> Parser TaskList -parser dataviewTaskCompletion filePath fileDate frontMatterTags bodyOffset = - P.loop [] (taskItemsHelp dataviewTaskCompletion filePath fileDate frontMatterTags bodyOffset) - |> P.map (\ts -> TaskList ts) +decoder : TsDecode.Decoder TaskList +decoder = + TsDecode.map TaskList (TsDecode.list TaskItem.decoder) + + +encoder : TsEncode.Encoder TaskList +encoder = + TsEncode.map topLevelTasks (TsEncode.list TaskItem.encoder) @@ -108,14 +129,20 @@ map fn = TaskList << List.map fn << topLevelTasks -replaceForFile : String -> TaskList -> TaskList -> TaskList -replaceForFile filePath updatedList = - append updatedList << removeForFile filePath - +replaceTaskItems : List ( String, TaskItem ) -> TaskList -> TaskList +replaceTaskItems replacementDetails taskList = + let + idsToRemove : List String + idsToRemove = + List.map Tuple.first replacementDetails -removeForFile : String -> TaskList -> TaskList -removeForFile filePath = - TaskList << itemsNotFromFile filePath << topLevelTasks + taskListToAdd : TaskList + taskListToAdd = + TaskList <| List.map Tuple.second replacementDetails + in + taskList + |> filter (\i -> not <| List.member (TaskItem.id i) idsToRemove) + |> append taskListToAdd @@ -129,6 +156,38 @@ containsTask taskId taskList = |> List.any (\ti -> TaskItem.id ti == taskId) +markdownDiffs : DataviewTaskCompletion -> MarkdownFile -> TaskList -> TaskListDiff +markdownDiffs dataviewTaskCompletion updatedMarkdown taskList = + let + updatedTasks : Dict String TaskItem + updatedTasks = + fromMarkdown dataviewTaskCompletion updatedMarkdown + |> topLevelTasks + |> List.map (\i -> ( TaskItem.id i ++ ":" ++ TaskItem.originalBlock i, i )) + |> Dict.fromList + + existingTasks : Dict String TaskItem + existingTasks = + filter (TaskItem.isFromFile updatedMarkdown.filePath) taskList + |> topLevelTasks + |> List.map (\i -> ( TaskItem.id i ++ ":" ++ TaskItem.originalBlock i, i )) + |> Dict.fromList + + toAdd : List TaskItem + toAdd = + Dict.diff updatedTasks existingTasks + |> Dict.toList + |> List.map Tuple.second + + toRemove : List TaskItem + toRemove = + Dict.diff existingTasks updatedTasks + |> Dict.toList + |> List.map Tuple.second + in + TaskListDiff toAdd toRemove + + taskTitles : TaskList -> List String taskTitles = List.map TaskItem.title << topLevelTasks @@ -141,16 +200,16 @@ taskIds = taskContainingId : String -> TaskList -> Maybe TaskItem taskContainingId id = - LE.find (TaskItem.containsId id) << tasks + LE.find (TaskItem.containsId id) << toList taskFromId : String -> TaskList -> Maybe TaskItem taskFromId id = - LE.find (\i -> TaskItem.id i == id) << tasks + LE.find (\i -> TaskItem.id i == id) << toList -tasks : TaskList -> List TaskItem -tasks = +toList : TaskList -> List TaskItem +toList = List.concatMap (\t -> t :: TaskItem.descendantTasks t) << topLevelTasks @@ -163,9 +222,10 @@ topLevelTasks (TaskList taskList) = -- PRIVATE -itemsNotFromFile : String -> List TaskItem -> List TaskItem -itemsNotFromFile pathToFile = - List.filter (\t -> not (TaskItem.isFromFile pathToFile t)) +parser : DataviewTaskCompletion -> String -> Maybe String -> TagList -> Int -> Parser TaskList +parser dataviewTaskCompletion filePath fileDate frontMatterTags bodyOffset = + P.loop [] (taskItemsHelp dataviewTaskCompletion filePath fileDate frontMatterTags bodyOffset) + |> P.map (\ts -> TaskList ts) taskItemsHelp : DataviewTaskCompletion -> String -> Maybe String -> TagList -> Int -> List TaskItem -> Parser (P.Step (List TaskItem) (List TaskItem)) diff --git a/src/UpdatedTaskItem.elm b/src/UpdatedTaskItem.elm index 7558954d..d8be9fb6 100644 --- a/src/UpdatedTaskItem.elm +++ b/src/UpdatedTaskItem.elm @@ -4,7 +4,7 @@ module UpdatedTaskItem exposing , dueString , init , lineNumber - , originalText + , originalLine , toString , toggleCompletion , updateDate @@ -106,9 +106,9 @@ lineNumber (UpdatedTaskItem _ taskItem) = TaskItem.lineNumber taskItem -originalText : UpdatedTaskItem -> String -originalText (UpdatedTaskItem _ taskItem) = - TaskItem.originalText taskItem +originalLine : UpdatedTaskItem -> String +originalLine (UpdatedTaskItem _ taskItem) = + TaskItem.originalLine taskItem toString : UpdatedTaskItem -> String @@ -153,7 +153,7 @@ toString ((UpdatedTaskItem change taskItem) as updatedTaskItem) = >> dataviewRemover in updatedTaskItem - |> originalText + |> originalLine |> replaceCheckbox taskItem |> removeCompletionTags |> insertBeforeBlockLink (completionTag taskCompletionSettings now taskItem) @@ -169,18 +169,18 @@ toString ((UpdatedTaskItem change taskItem) as updatedTaskItem) = case newDate of Nothing -> updatedTaskItem - |> originalText + |> originalLine |> removeDueTags |> insertBeforeBlockLink (noneTagIfHasFileDate taskItem) Just date -> updatedTaskItem - |> originalText + |> originalLine |> removeDueTags |> insertBeforeBlockLink (dueTag taskCompletionSettings date taskItem) NoChange -> - originalText updatedTaskItem + originalLine updatedTaskItem noneTagIfHasFileDate : TaskItem -> String diff --git a/src/Worker.elm b/src/Worker.elm new file mode 100644 index 00000000..e41d700d --- /dev/null +++ b/src/Worker.elm @@ -0,0 +1,190 @@ +module Worker exposing (Model, Msg, main) + +import Json.Decode as JD +import List.Extra as LE +import MarkdownFile exposing (MarkdownFile) +import TaskItem exposing (TaskItem) +import TaskList exposing (TaskList, TaskListDiff) +import Worker.InteropDefinitions as InteropDefinitions +import Worker.InteropPorts as InteropPorts +import Worker.Session as Session exposing (Session) + + +main : Program JD.Value Model Msg +main = + Platform.worker + { init = init + , update = update + , subscriptions = subscriptions + } + + +type Model + = FlagsError Session + | Yeah Session + + +init : JD.Value -> ( Model, Cmd Msg ) +init flags = + case flags |> InteropPorts.decodeFlags of + Err _ -> + ( FlagsError Session.default, Cmd.none ) + + Ok okFlags -> + let + session : Session + session = + Session.fromFlags okFlags + in + ( Yeah session + , Cmd.none + ) + + +toSession : Model -> Session +toSession model = + case model of + FlagsError session -> + session + + Yeah session -> + session + + +mapSession : (Session -> Session) -> Model -> Model +mapSession fn model = + case model of + FlagsError session -> + FlagsError <| fn session + + Yeah session -> + Yeah <| fn session + + + +-- UPDATE + + +type Msg + = AllMarkdownLoaded + | ViewInitialized + | BadInputFromTypeScript + | VaultFileAdded MarkdownFile + | VaultFileDeleted String + | VaultFileModified MarkdownFile + | VaultFileRenamed ( String, String ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + AllMarkdownLoaded -> + ( mapSession Session.finishAdding model, InteropPorts.allTasksLoaded ) + + ViewInitialized -> + ( model, InteropPorts.allTaskItems <| Session.taskList <| toSession model ) + + BadInputFromTypeScript -> + ( model, Cmd.none ) + + VaultFileAdded markdownFile -> + let + newTasks : TaskList + newTasks = + TaskList.fromMarkdown (Session.dataviewTaskCompletion <| toSession model) markdownFile + in + ( mapSession (Session.addTaskList newTasks) model + , InteropPorts.tasksAdded newTasks + ) + + VaultFileDeleted filePath -> + let + ( toDelete, remaining ) = + toSession model + |> Session.taskList + |> TaskList.topLevelTasks + |> List.partition (TaskItem.isFromFile filePath) + in + ( mapSession (Session.replaceTaskList <| TaskList.fromList remaining) model + , InteropPorts.tasksDeleted toDelete + ) + + VaultFileModified markdownFile -> + let + deleteIds : List String + deleteIds = + List.map TaskItem.id taskListDiff.toDelete + + session : Session + session = + toSession model + + taskListDiff : TaskListDiff + taskListDiff = + TaskList.markdownDiffs + (Session.dataviewTaskCompletion session) + markdownFile + (Session.taskList session) + in + ( model + |> mapSession (Session.removeTaskItems deleteIds) + |> mapSession (Session.addTaskList <| TaskList.fromList taskListDiff.toAdd) + , InteropPorts.tasksDeletedAndAdded deleteIds taskListDiff.toAdd + ) + + VaultFileRenamed ( oldPath, newPath ) -> + let + originalIds : List String + originalIds = + List.map TaskItem.id toUpdate + + updatedTaskItems : List TaskItem + updatedTaskItems = + List.map (TaskItem.updateFilePath oldPath newPath) toUpdate + + ( toUpdate, remaining ) = + toSession model + |> Session.taskList + |> TaskList.topLevelTasks + |> List.partition (TaskItem.isFromFile oldPath) + in + ( mapSession (Session.replaceTaskList <| TaskList.fromList (remaining ++ updatedTaskItems)) model + , InteropPorts.tasksUpdated (LE.zip originalIds updatedTaskItems) + ) + + + +-- SUBSCRIPTIONS + + +subscriptions : Model -> Sub Msg +subscriptions _ = + Sub.batch + [ InteropPorts.toElm + |> Sub.map + (\result -> + case result of + Ok toElm -> + case toElm of + InteropDefinitions.AllMarkdownLoaded -> + AllMarkdownLoaded + + InteropDefinitions.ViewInitialized -> + ViewInitialized + + InteropDefinitions.FileAdded markdownFile -> + VaultFileAdded markdownFile + + InteropDefinitions.FileDeleted filePath -> + VaultFileDeleted filePath + + InteropDefinitions.FileModified markdownFile -> + VaultFileModified markdownFile + + InteropDefinitions.FileRenamed oldAndNewPath -> + VaultFileRenamed oldAndNewPath + + Err _ -> + BadInputFromTypeScript + ) + ] diff --git a/src/Worker/InteropDefinitions.elm b/src/Worker/InteropDefinitions.elm new file mode 100644 index 00000000..09e74e9f --- /dev/null +++ b/src/Worker/InteropDefinitions.elm @@ -0,0 +1,124 @@ +module Worker.InteropDefinitions exposing + ( Flags + , FromElm(..) + , ToElm(..) + , interop + ) + +import DataviewTaskCompletion exposing (DataviewTaskCompletion) +import DecodeHelpers +import MarkdownFile exposing (MarkdownFile) +import TaskItem exposing (TaskItem) +import TaskList exposing (TaskList) +import TsJson.Decode as TsDecode +import TsJson.Encode as TsEncode + + +type FromElm + = AllTasksLoaded + | AllTaskItems TaskList + | TasksAdded TaskList + | TasksDeleted (List TaskItem) + | TasksDeletedAndAdded ( List String, List TaskItem ) + | TasksUpdated (List ( String, TaskItem )) + + +type ToElm + = AllMarkdownLoaded + | ViewInitialized + | FileAdded MarkdownFile + | FileDeleted String + | FileModified MarkdownFile + | FileRenamed ( String, String ) + + +type alias Flags = + { dataviewTaskCompletion : DataviewTaskCompletion } + + +interop : { toElm : TsDecode.Decoder ToElm, fromElm : TsEncode.Encoder FromElm, flags : TsDecode.Decoder Flags } +interop = + { toElm = toElm + , fromElm = fromElm + , flags = flags + } + + + +-- INTEROP + + +flags : TsDecode.Decoder Flags +flags = + TsDecode.succeed Flags + |> TsDecode.andMap (TsDecode.field "dataviewTaskCompletion" DataviewTaskCompletion.decoder) + + +toElm : TsDecode.Decoder ToElm +toElm = + TsDecode.oneOf + [ DecodeHelpers.toElmVariant "allMarkdownLoaded" (always AllMarkdownLoaded) (TsDecode.succeed ()) + , DecodeHelpers.toElmVariant0 "viewInitialized" ViewInitialized + , DecodeHelpers.toElmVariant "fileAdded" FileAdded MarkdownFile.decoder + , DecodeHelpers.toElmVariant "fileDeleted" FileDeleted TsDecode.string + , DecodeHelpers.toElmVariant "fileModified" FileModified MarkdownFile.decoder + , DecodeHelpers.toElmVariant "fileRenamed" FileRenamed renamedFileDecoder + ] + + +fromElm : TsEncode.Encoder FromElm +fromElm = + TsEncode.union + (\vAllTasksLoaded vAllTaskItems vTasksAdded vTasksDeleted vTasksDeletedAndAdded vTasksUpdated value -> + case value of + AllTasksLoaded -> + vAllTasksLoaded + + AllTaskItems taskList -> + vAllTaskItems taskList + + TasksAdded taskList -> + vTasksAdded taskList + + TasksDeleted taskItems -> + vTasksDeleted taskItems + + TasksDeletedAndAdded toDeletedAndAdd -> + vTasksDeletedAndAdded toDeletedAndAdd + + TasksUpdated tasksToUpdate -> + vTasksUpdated tasksToUpdate + ) + |> TsEncode.variant0 "allTasksLoaded" + |> TsEncode.variantTagged "allTaskItems" TaskList.encoder + |> TsEncode.variantTagged "tasksAdded" TaskList.encoder + |> TsEncode.variantTagged "tasksDeleted" tasksDeletedEncoder + |> TsEncode.variantTagged "tasksDeletedAndAdded" tasksDeletedAndAddedEncoder + |> TsEncode.variantTagged "tasksUpdated" tasksUpdatedEncoder + |> TsEncode.buildUnion + + + +-- HELPERS + + +renamedFileDecoder : TsDecode.Decoder ( String, String ) +renamedFileDecoder = + TsDecode.succeed Tuple.pair + |> TsDecode.andMap (TsDecode.field "oldPath" TsDecode.string) + |> TsDecode.andMap (TsDecode.field "newPath" TsDecode.string) + + +tasksDeletedAndAddedEncoder : TsEncode.Encoder ( List String, List TaskItem ) +tasksDeletedAndAddedEncoder = + TsEncode.tuple (TsEncode.list TsEncode.string) (TsEncode.list TaskItem.encoder) + + +tasksDeletedEncoder : TsEncode.Encoder (List TaskItem) +tasksDeletedEncoder = + TsEncode.list <| TsEncode.map TaskItem.id TsEncode.string + + +tasksUpdatedEncoder : TsEncode.Encoder (List ( String, TaskItem )) +tasksUpdatedEncoder = + TsEncode.list <| TsEncode.tuple TsEncode.string TaskItem.encoder diff --git a/src/Worker/InteropPorts.elm b/src/Worker/InteropPorts.elm new file mode 100644 index 00000000..fcd815f5 --- /dev/null +++ b/src/Worker/InteropPorts.elm @@ -0,0 +1,97 @@ +port module Worker.InteropPorts exposing + ( allTaskItems + , allTasksLoaded + , decodeFlags + , tasksAdded + , tasksDeleted + , tasksDeletedAndAdded + , tasksUpdated + , toElm + ) + +import Json.Decode +import Json.Encode +import TaskItem exposing (TaskItem) +import TaskList exposing (TaskList) +import TsJson.Decode as TsDecode +import TsJson.Encode as TsEncode +import Worker.InteropDefinitions as InteropDefinitions + + +toElm : Sub (Result Json.Decode.Error InteropDefinitions.ToElm) +toElm = + (InteropDefinitions.interop.toElm |> TsDecode.decoder) + |> Json.Decode.decodeValue + |> interopToElm + + + +-- FLAGS + + +decodeFlags : Json.Decode.Value -> Result Json.Decode.Error InteropDefinitions.Flags +decodeFlags flags = + Json.Decode.decodeValue + (InteropDefinitions.interop.flags |> TsDecode.decoder) + flags + + + +-- COMMANDS + + +allTaskItems : TaskList -> Cmd msg +allTaskItems taskList = + taskList + |> InteropDefinitions.AllTaskItems + |> TsEncode.encoder InteropDefinitions.interop.fromElm + |> interopFromElm + + +allTasksLoaded : Cmd msg +allTasksLoaded = + InteropDefinitions.AllTasksLoaded + |> TsEncode.encoder InteropDefinitions.interop.fromElm + |> interopFromElm + + +tasksAdded : TaskList -> Cmd msg +tasksAdded taskList = + taskList + |> InteropDefinitions.TasksAdded + |> TsEncode.encoder InteropDefinitions.interop.fromElm + |> interopFromElm + + +tasksDeleted : List TaskItem -> Cmd msg +tasksDeleted taskItems = + taskItems + |> InteropDefinitions.TasksDeleted + |> TsEncode.encoder InteropDefinitions.interop.fromElm + |> interopFromElm + + +tasksDeletedAndAdded : List String -> List TaskItem -> Cmd msg +tasksDeletedAndAdded toDelete toAdd = + ( toDelete, toAdd ) + |> InteropDefinitions.TasksDeletedAndAdded + |> TsEncode.encoder InteropDefinitions.interop.fromElm + |> interopFromElm + + +tasksUpdated : List ( String, TaskItem ) -> Cmd msg +tasksUpdated updateDetails = + updateDetails + |> InteropDefinitions.TasksUpdated + |> TsEncode.encoder InteropDefinitions.interop.fromElm + |> interopFromElm + + + +-- PORTS + + +port interopFromElm : Json.Encode.Value -> Cmd msg + + +port interopToElm : (Json.Decode.Value -> msg) -> Sub msg diff --git a/src/Worker/Session.elm b/src/Worker/Session.elm new file mode 100644 index 00000000..c0d66d1b --- /dev/null +++ b/src/Worker/Session.elm @@ -0,0 +1,138 @@ +module Worker.Session exposing + ( Session + , addTaskList + , dataviewTaskCompletion + , default + , finishAdding + , fromFlags + , removeTaskItems + , replaceTaskList + , taskList + ) + +import DataviewTaskCompletion exposing (DataviewTaskCompletion) +import State exposing (State) +import TaskItem +import TaskList exposing (TaskList) +import Worker.InteropDefinitions as InteropDefinitions + + + +-- TYPES + + +type Session + = Session Model + + +type alias Model = + { dataviewTaskCompletion : DataviewTaskCompletion + , taskList : State TaskList + } + + + +-- CREATE + + +default : Session +default = + Session + { dataviewTaskCompletion = DataviewTaskCompletion.default + , taskList = State.Waiting + } + + +fromFlags : InteropDefinitions.Flags -> Session +fromFlags flags = + Session + { dataviewTaskCompletion = flags.dataviewTaskCompletion + , taskList = State.Waiting + } + + + +-- INFO + + +dataviewTaskCompletion : Session -> DataviewTaskCompletion +dataviewTaskCompletion (Session model) = + model.dataviewTaskCompletion + + +taskList : Session -> TaskList +taskList (Session model) = + case model.taskList of + State.Waiting -> + TaskList.empty + + State.Loading currentList -> + currentList + + State.Loaded currentList -> + currentList + + + +-- TASKLIST MANIPULATION + + +addTaskList : TaskList -> Session -> Session +addTaskList list ((Session model) as session) = + case model.taskList of + State.Waiting -> + updateTaskListState (State.Loading list) session + + State.Loading currentList -> + updateTaskListState (State.Loading (TaskList.append currentList list)) session + + State.Loaded currentList -> + updateTaskListState (State.Loaded (TaskList.append currentList list)) session + + +finishAdding : Session -> Session +finishAdding ((Session config) as session) = + case config.taskList of + State.Waiting -> + updateTaskListState (State.Loaded TaskList.empty) session + + State.Loading list -> + updateTaskListState (State.Loaded list) session + + State.Loaded _ -> + session + + +removeTaskItems : List String -> Session -> Session +removeTaskItems taskIds ((Session config) as session) = + case config.taskList of + State.Waiting -> + session + + State.Loading currentList -> + updateTaskListState (State.Loading (TaskList.filter (\i -> not <| List.member (TaskItem.id i) taskIds) currentList)) session + + State.Loaded currentList -> + updateTaskListState (State.Loaded (TaskList.filter (\i -> not <| List.member (TaskItem.id i) taskIds) currentList)) session + + +replaceTaskList : TaskList -> Session -> Session +replaceTaskList newList ((Session config) as session) = + case config.taskList of + State.Waiting -> + session + + State.Loading _ -> + updateTaskListState (State.Loading newList) session + + State.Loaded _ -> + updateTaskListState (State.Loaded newList) session + + + +-- PRIVATE + + +updateTaskListState : State TaskList -> Session -> Session +updateTaskListState taskListState (Session model) = + Session { model | taskList = taskListState } diff --git a/tests/DecodeHelpersTests.elm b/tests/DecodeHelpersTests.elm new file mode 100644 index 00000000..300f5db0 --- /dev/null +++ b/tests/DecodeHelpersTests.elm @@ -0,0 +1,26 @@ +module DecodeHelpersTests exposing (suite) + +import Date +import DecodeHelpers +import Expect +import Helpers.DecodeHelpers as DecodeTestHelpers +import Test exposing (..) + + +suite : Test +suite = + concat + [ decoder + ] + + +decoder : Test +decoder = + describe "decoder" + [ test "decodes a RataDie integer as a date" <| + \() -> + "123456" + |> DecodeTestHelpers.runDecoder DecodeHelpers.dateDecoder + |> .decoded + |> Expect.equal (Ok <| Date.fromRataDie 123456) + ] diff --git a/tests/DueDateTests.elm b/tests/DueDateTests.elm new file mode 100644 index 00000000..a2ac88d3 --- /dev/null +++ b/tests/DueDateTests.elm @@ -0,0 +1,64 @@ +module DueDateTests exposing (suite) + +import Date +import DueDate +import Expect +import Helpers.DecodeHelpers as DecodeTestHelpers +import Test exposing (..) +import TsJson.Encode as TsEncode + + +suite : Test +suite = + concat + [ decoder + , encoder + ] + + +decoder : Test +decoder = + describe "decoder" + [ test "decodes a NotSet" <| + \() -> + """{"tag":"NotSet"}""" + |> DecodeTestHelpers.runDecoder DueDate.decoder + |> .decoded + |> Expect.equal (Ok DueDate.NotSet) + , test "decodes a SetToDate" <| + \() -> + """{"tag":"SetToDate","date":123456}""" + |> DecodeTestHelpers.runDecoder DueDate.decoder + |> .decoded + |> Expect.equal (Ok <| DueDate.SetToDate (Date.fromRataDie 123456)) + , test "decodes a SetToNone" <| + \() -> + """{"tag":"SetToNone"}""" + |> DecodeTestHelpers.runDecoder DueDate.decoder + |> .decoded + |> Expect.equal (Ok DueDate.SetToNone) + ] + + +encoder : Test +encoder = + describe "encoder" + [ test "encodes a NotSet" <| + \() -> + DueDate.NotSet + |> TsEncode.runExample DueDate.encoder + |> .output + |> Expect.equal "{\"tag\":\"NotSet\"}" + , test "encodes a SetToDate" <| + \() -> + DueDate.SetToDate (Date.fromRataDie 123123) + |> TsEncode.runExample DueDate.encoder + |> .output + |> Expect.equal "{\"tag\":\"SetToDate\",\"date\":123123}" + , test "encodes a SetToNone" <| + \() -> + DueDate.SetToNone + |> TsEncode.runExample DueDate.encoder + |> .output + |> Expect.equal "{\"tag\":\"SetToNone\"}" + ] diff --git a/tests/EncodeHelpersTests.elm b/tests/EncodeHelpersTests.elm new file mode 100644 index 00000000..2d767372 --- /dev/null +++ b/tests/EncodeHelpersTests.elm @@ -0,0 +1,26 @@ +module EncodeHelpersTests exposing (suite) + +import Date +import EncodeHelpers +import Expect +import Test exposing (..) +import TsJson.Encode as TsEncode + + +suite : Test +suite = + concat + [ encoder + ] + + +encoder : Test +encoder = + describe "encoder" + [ test "encodes the date as a RataDie integer" <| + \() -> + Date.fromRataDie 123456 + |> TsEncode.runExample EncodeHelpers.dateEncoder + |> .output + |> Expect.equal "123456" + ] diff --git a/tests/Helpers/TaskItemHelpers.elm b/tests/Helpers/TaskItemHelpers.elm index e24cd64f..ac05715c 100644 --- a/tests/Helpers/TaskItemHelpers.elm +++ b/tests/Helpers/TaskItemHelpers.elm @@ -1,6 +1,7 @@ module Helpers.TaskItemHelpers exposing ( basicParser , exampleTaskItem + , safeTaskItem ) import DataviewTaskCompletion @@ -18,3 +19,9 @@ exampleTaskItem : String -> String -> TaskItem exampleTaskItem markdown path = Parser.run (TaskItem.parser DataviewTaskCompletion.NoCompletion path Nothing TagList.empty 0) markdown |> Result.withDefault TaskItem.dummy + + +safeTaskItem : String -> TaskItem +safeTaskItem markdown = + Parser.run basicParser markdown + |> Result.withDefault TaskItem.dummy diff --git a/tests/Helpers/TaskListHelpers.elm b/tests/Helpers/TaskListHelpers.elm index 5f138d28..51de28a5 100644 --- a/tests/Helpers/TaskListHelpers.elm +++ b/tests/Helpers/TaskListHelpers.elm @@ -1,6 +1,5 @@ module Helpers.TaskListHelpers exposing - ( basicParser - , exampleDateBoardTaskList + ( exampleDateBoardTaskList , exampleTagBoardTaskList , parsedTasks , taskListFromFile @@ -11,20 +10,29 @@ module Helpers.TaskListHelpers exposing import DataviewTaskCompletion import Helpers.DateTimeHelpers as DateTimeHelpers -import Parser exposing (Parser) +import MarkdownFile exposing (MarkdownFile) import TagList import TaskList exposing (TaskList) -basicParser : Parser TaskList -basicParser = - TaskList.parser DataviewTaskCompletion.NoCompletion "" Nothing TagList.empty 0 +exampleDateBoardTaskList : TaskList +exampleDateBoardTaskList = + exampleDateBoardTasks + |> List.map parsedTasks + |> TaskList.concat + + +exampleTagBoardTaskList : TaskList +exampleTagBoardTaskList = + exampleTagBoardTasks + |> List.map parsedTasks + |> TaskList.concat parsedTasks : ( String, Maybe String, String ) -> TaskList parsedTasks ( p, d, ts ) = - Parser.run (TaskList.parser DataviewTaskCompletion.NoCompletion p d TagList.empty 0) ts - |> Result.withDefault TaskList.empty + basicMarkdown p d ts + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion taskListFromFile : String -> TaskList @@ -51,20 +59,6 @@ taskListFromNewFile path = |> parsedTasks -exampleDateBoardTaskList : TaskList -exampleDateBoardTaskList = - exampleDateBoardTasks - |> List.map parsedTasks - |> TaskList.concat - - -exampleTagBoardTaskList : TaskList -exampleTagBoardTaskList = - exampleTagBoardTasks - |> List.map parsedTasks - |> TaskList.concat - - -- HELPERS @@ -189,3 +183,17 @@ exampleTagBoardTasks = - [x] c.tag4 #tag4 """ ) ] + + + +-- HELPERS + + +basicMarkdown : String -> Maybe String -> String -> MarkdownFile +basicMarkdown path date body = + { filePath = path + , fileDate = date + , frontMatterTags = TagList.empty + , bodyOffset = 0 + , body = body + } diff --git a/tests/InteropDefinitionsTests.elm b/tests/InteropDefinitionsTests.elm index b0971dcd..6e32ac6b 100644 --- a/tests/InteropDefinitionsTests.elm +++ b/tests/InteropDefinitionsTests.elm @@ -14,10 +14,13 @@ import Filter import GlobalSettings import Helpers.DecodeHelpers as DecodeHelpers import Helpers.FilterHelpers as FilterHelpers +import Helpers.TaskItemHelpers as TaskItemHelpers import InteropDefinitions exposing (interop) +import Parser import SafeZipper import Semver -import TagList +import TaskItem exposing (TaskItem, TaskItemFields) +import TaskList exposing (TaskList) import Test exposing (..) import TextDirection import Time @@ -994,11 +997,11 @@ fromElmTests = |> Expect.equal """{"tag":"closeView"}""" , test "encodes DeleteTask data" <| \() -> - { filePath = "a path", lineNumber = 33, originalText = "the text" } + { defaultTaskItemFields | filePath = "a path", lineNumber = 33, originalLine = "the text" } |> InteropDefinitions.DeleteTask |> TsEncode.runExample interop.fromElm |> .output - |> Expect.equal """{"tag":"deleteTask","data":{"filePath":"a path","lineNumber":33,"originalText":"the text"}}""" + |> Expect.equal """{"tag":"deleteTask","data":{"filePath":"a path","lineNumber":33,"originalLine":"the text"}}""" , test "encodes DisplayTaskMarkdown data" <| \() -> [ { filePath = "a path", taskMarkdown = [ { id = "an id", markdown = "some markdown" } ] } ] @@ -1008,17 +1011,18 @@ fromElmTests = |> Expect.equal """{"tag":"displayTaskMarkdown","data":[{"filePath":"a path","taskMarkdown":[{"id":"an id","markdown":"some markdown"}]}]}""" , test "encodes ElmInitialized" <| \() -> - InteropDefinitions.ElmInitialized + "a_unique_id" + |> InteropDefinitions.ElmInitialized |> TsEncode.runExample interop.fromElm |> .output - |> Expect.equal """{"tag":"elmInitialized"}""" + |> Expect.equal """{"tag":"elmInitialized","data":"a_unique_id"}""" , test "encodes OpenTaskSourceFile data" <| \() -> - { filePath = "a path", lineNumber = 33, originalText = "the text" } + { defaultTaskItemFields | filePath = "a path", lineNumber = 33, originalLine = "the text" } |> InteropDefinitions.OpenTaskSourceFile |> TsEncode.runExample interop.fromElm |> .output - |> Expect.equal """{"tag":"openTaskSourceFile","data":{"filePath":"a path","lineNumber":33,"originalText":"the text"}}""" + |> Expect.equal """{"tag":"openTaskSourceFile","data":{"filePath":"a path","lineNumber":33,"originalLine":"the text"}}""" , test "encodes RequestFilterCandidates" <| \() -> InteropDefinitions.RequestFilterCandidates @@ -1041,11 +1045,11 @@ fromElmTests = |> Expect.equal """{"tag":"trackDraggable","data":{"dragType":"someDragType","clientPos":{"x":1.1,"y":2.2},"draggableId":"id of draggable"}}""" , test "encodes UpdateTasks data" <| \() -> - { filePath = "a path", tasks = [ { lineNumber = 12, originalText = "what was there", newText = "new text" } ] } + { filePath = "a path", tasks = [ { lineNumber = 12, originalLine = "what was there", newText = "new text" } ] } |> InteropDefinitions.UpdateTasks |> TsEncode.runExample interop.fromElm |> .output - |> Expect.equal """{"tag":"updateTasks","data":{"filePath":"a path","tasks":[{"lineNumber":12,"originalText":"what was there","newText":"new text"}]}}""" + |> Expect.equal """{"tag":"updateTasks","data":{"filePath":"a path","tasks":[{"lineNumber":12,"originalLine":"what was there","newText":"new text"}]}}""" ] @@ -1058,12 +1062,6 @@ toElmTests = |> DecodeHelpers.runDecoder interop.toElm |> .decoded |> Expect.equal (Ok <| InteropDefinitions.ActiveStateUpdated False) - , test "decodes allMarkdownLoaded" <| - \() -> - """{"tag":"allMarkdownLoaded","data":{}}""" - |> DecodeHelpers.runDecoder interop.toElm - |> .decoded - |> Expect.equal (Ok <| InteropDefinitions.AllMarkdownLoaded) , test "decodes configChanged data" <| \() -> """{"tag":"configChanged","data":{"rightToLeft":true}}""" @@ -1090,54 +1088,12 @@ toElmTests = ] } ) - , test "decodes fileAdded data" <| - \() -> - """{"tag":"fileAdded","data":{"filePath":"a path","fileDate":"a date","fileContents":"---\\ntags: [ a_tag ]\\n---\\nsome contents"}}""" - |> DecodeHelpers.runDecoder interop.toElm - |> .decoded - |> Expect.equal - (Ok <| - InteropDefinitions.FileAdded - { filePath = "a path" - , fileDate = Just "a date" - , frontMatterTags = TagList.fromList [ "a_tag" ] - , bodyOffset = 3 - , body = "some contents" - } - ) - , test "decodes fileDeleted data" <| - \() -> - """{"tag":"fileDeleted","data":"a path"}""" - |> DecodeHelpers.runDecoder interop.toElm - |> .decoded - |> Expect.equal (Ok <| InteropDefinitions.FileDeleted "a path") , test "decodes editCardDueDate data" <| \() -> """{"tag":"editCardDueDate","data":"a card"}""" |> DecodeHelpers.runDecoder interop.toElm |> .decoded |> Expect.equal (Ok <| InteropDefinitions.EditCardDueDate "a card") - , test "decodes fileRenamed data" <| - \() -> - """{"tag":"fileRenamed","data":{"oldPath":"the old path","newPath":"the new path"}}""" - |> DecodeHelpers.runDecoder interop.toElm - |> .decoded - |> Expect.equal (Ok <| InteropDefinitions.FileRenamed ( "the old path", "the new path" )) - , test "decodes fileUpdated data" <| - \() -> - """{"tag":"fileUpdated","data":{"filePath":"a path","fileDate":"a date","frontMatterTags":["a_tag"],"fileContents":"---\\ntags: [ a_tag ]\\n---\\nsome contents"}}""" - |> DecodeHelpers.runDecoder interop.toElm - |> .decoded - |> Expect.equal - (Ok <| - InteropDefinitions.FileUpdated - { filePath = "a path" - , fileDate = Just "a date" - , frontMatterTags = TagList.fromList [ "a_tag" ] - , bodyOffset = 3 - , body = "some contents" - } - ) , test "decodes filterCandidates data" <| \() -> """{"tag":"filterCandidates","data":[{"tag":"pathFilter","data":"a path"},{"tag":"pathFilter","data":"another path"}]}""" @@ -1212,4 +1168,68 @@ toElmTests = |> .decoded |> Result.toMaybe |> Expect.equal Nothing + , test "decodes taskItemsAdded data" <| + \() -> + let + taskList : TaskList + taskList = + TaskList.empty + |> TaskList.add (taskItem "- [ ] foo") + in + """{"tag":"taskItemsAdded","data":[{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] foo","originalLine":"- [ ] foo","tags":[],"title":["foo"]},"subFields":[]}]}""" + |> DecodeHelpers.runDecoder interop.toElm + |> .decoded + |> Expect.equal (Ok <| InteropDefinitions.TaskItemsAdded taskList) + , test "decodes taskItemsDeleted data" <| + \() -> + """{"tag":"taskItemsDeleted","data":["foo","bar","baz"]}""" + |> DecodeHelpers.runDecoder interop.toElm + |> .decoded + |> Expect.equal (Ok <| InteropDefinitions.TaskItemsDeleted [ "foo", "bar", "baz" ]) + , test "decodes taskItemsDeletedAndAdded data" <| + \() -> + let + toAdd : List TaskItem + toAdd = + TaskList.empty + |> TaskList.add (taskItem "- [ ] bar") + |> TaskList.toList + in + """{"tag":"taskItemsDeletedAndAdded","data":[["taskId:1"],[{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"bar"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] bar","originalLine":"- [ ] bar","tags":[],"title":["bar"]},"subFields":[]}]]}""" + |> DecodeHelpers.runDecoder interop.toElm + |> .decoded + |> Expect.equal (Ok <| InteropDefinitions.TaskItemsDeletedAndAdded ( [ "taskId:1" ], toAdd )) + , test "decodes taskItemsRefreshed data" <| + \() -> + let + taskList : TaskList + taskList = + TaskList.empty + |> TaskList.add (taskItem "- [ ] foo") + in + """{"tag":"taskItemsRefreshed","data":[{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] foo","originalLine":"- [ ] foo","tags":[],"title":["foo"]},"subFields":[]}]}""" + |> DecodeHelpers.runDecoder interop.toElm + |> .decoded + |> Expect.equal (Ok <| InteropDefinitions.TaskItemsRefreshed taskList) + , test "decodes taskItemsUpdated data" <| + \() -> + """{"tag":"taskItemsUpdated","data":[["bar",{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] foo","originalLine":"- [ ] foo","tags":[],"title":["foo"]},"subFields":[]}]]}""" + |> DecodeHelpers.runDecoder interop.toElm + |> .decoded + |> Expect.equal (Ok <| InteropDefinitions.TaskItemsUpdated [ ( "bar", taskItem "- [ ] foo" ) ]) ] + + + +-- HELPERS + + +defaultTaskItemFields : TaskItemFields +defaultTaskItemFields = + TaskItem.fields TaskItem.dummy + + +taskItem : String -> TaskItem +taskItem markdown = + Parser.run TaskItemHelpers.basicParser markdown + |> Result.withDefault TaskItem.dummy diff --git a/tests/SessionTests.elm b/tests/SessionTests.elm index c14545c9..9f50aee6 100644 --- a/tests/SessionTests.elm +++ b/tests/SessionTests.elm @@ -12,12 +12,14 @@ import Expect import Filter import GlobalSettings exposing (GlobalSettings) import Helpers.FilterHelpers as FilterHelpers +import Helpers.TaskItemHelpers as TaskItemHelpers import Helpers.TaskListHelpers as TaskListHelpers import InteropDefinitions exposing (Flags) +import Parser import SafeZipper import Session import Settings exposing (Settings) -import TaskItem +import TaskItem exposing (TaskItem) import TaskList import Test exposing (..) import Time @@ -29,14 +31,15 @@ suite = [ addTaskList , cards , default - , deleteItemsFromFile , findCard , finishAdding , firstDayOfWeek , fromFlags , globalSettings , moveDragable + , removeTaskItems , replaceTaskItems + , replaceTaskList , stopTrackingDragable , updatePath , waitForDrag @@ -141,38 +144,6 @@ default = ] -deleteItemsFromFile : Test -deleteItemsFromFile = - describe "deleteItemsFromFile" - [ test "does nothing to the tasklist of a new session" <| - \() -> - Session.default - |> Session.deleteItemsFromFile "" - |> Session.taskList - |> TaskList.taskTitles - |> Expect.equal [] - , test "remove tasks from the given file during loading" <| - \() -> - Session.default - |> Session.addTaskList TaskListHelpers.taskListFromFileA - |> Session.addTaskList TaskListHelpers.taskListFromFileG - |> Session.deleteItemsFromFile "g" - |> Session.taskList - |> TaskList.taskTitles - |> Expect.equal [ "a1", "a2" ] - , test "remove tasks from the given file after loading has finished" <| - \() -> - Session.default - |> Session.addTaskList TaskListHelpers.taskListFromFileA - |> Session.addTaskList TaskListHelpers.taskListFromFileG - |> Session.finishAdding - |> Session.deleteItemsFromFile "g" - |> Session.taskList - |> TaskList.taskTitles - |> Expect.equal [ "a1", "a2" ] - ] - - findCard : Test findCard = describe "findCard" @@ -351,13 +322,47 @@ moveDragable = ] +removeTaskItems : Test +removeTaskItems = + describe "removeTaskItems" + [ test "does nothing if there are no taskItems in the Session" <| + \() -> + Session.default + |> Session.removeTaskItems [] + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [] + , test "removes tasks with the given ids whilst loading tasklists" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.addTaskList TaskListHelpers.taskListFromFileG + |> Session.removeTaskItems [ "3826002220:2", "3792446982:3" ] + |> Session.taskList + |> TaskList.toList + |> List.map TaskItem.id + |> Expect.equal [ "3826002220:3", "3792446982:2" ] + , test "removes tasks with the given ids if finished loading tasklists" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.addTaskList TaskListHelpers.taskListFromFileG + |> Session.finishAdding + |> Session.removeTaskItems [ "3826002220:2", "3792446982:3" ] + |> Session.taskList + |> TaskList.toList + |> List.map TaskItem.id + |> Expect.equal [ "3826002220:3", "3792446982:2" ] + ] + + replaceTaskItems : Test replaceTaskItems = describe "replaceTaskItems" [ test "does nothing on a new session" <| \() -> Session.default - |> Session.replaceTaskItems "" TaskListHelpers.taskListFromFileA + |> Session.replaceTaskItems [] |> Session.taskList |> TaskList.taskTitles |> Expect.equal [] @@ -366,20 +371,54 @@ replaceTaskItems = Session.default |> Session.addTaskList TaskListHelpers.taskListFromFileA |> Session.addTaskList TaskListHelpers.taskListFromFileG - |> Session.replaceTaskItems "a" (TaskListHelpers.taskListFromNewFile "path") + |> Session.replaceTaskItems [ ( "3826002220:3", taskItem "- [ ] foo" ) ] |> Session.taskList |> TaskList.taskTitles - |> Expect.equal [ "n1", "n2", "g1", "g2" ] - , test "replaces tasks from the file with those given whilst loading tasklists even if finished adding" <| + |> List.sort + |> Expect.equal (List.sort [ "a1", "foo", "g1", "g2" ]) + , test "replaces tasks from the file with those given when finished loading tasklists" <| \() -> Session.default |> Session.addTaskList TaskListHelpers.taskListFromFileA |> Session.addTaskList TaskListHelpers.taskListFromFileG |> Session.finishAdding - |> Session.replaceTaskItems "g" (TaskListHelpers.taskListFromNewFile "path") + |> Session.replaceTaskItems [ ( "3826002220:3", taskItem "- [ ] foo" ) ] |> Session.taskList |> TaskList.taskTitles - |> Expect.equal [ "n1", "n2", "a1", "a2" ] + |> List.sort + |> Expect.equal (List.sort [ "a1", "foo", "g1", "g2" ]) + ] + + +replaceTaskList : Test +replaceTaskList = + describe "replaceTaskList" + [ test "replaces all tasks with those given if a new session" <| + \() -> + Session.default + |> Session.replaceTaskList TaskListHelpers.taskListFromFileA + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [ "a1", "a2" ] + , test "replaces all tasks with those given whilst loading tasklists" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.addTaskList TaskListHelpers.taskListFromFileG + |> Session.replaceTaskList (TaskListHelpers.taskListFromNewFile "path") + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [ "n1", "n2" ] + , test "replaces all tasks with those given whilst loading tasklists even if finished adding" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.addTaskList TaskListHelpers.taskListFromFileG + |> Session.finishAdding + |> Session.replaceTaskList (TaskListHelpers.taskListFromNewFile "path") + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [ "n1", "n2" ] ] @@ -444,6 +483,11 @@ updatePath = -- HELPERS +defaultGlobalSettings : GlobalSettings +defaultGlobalSettings = + GlobalSettings.default + + exampleFlags : Flags exampleFlags = { dataviewTaskCompletion = DataviewTaskCompletion.NoCompletion @@ -481,9 +525,10 @@ exampleSettings = ) -defaultGlobalSettings : GlobalSettings -defaultGlobalSettings = - GlobalSettings.default +taskItem : String -> TaskItem +taskItem markdown = + Parser.run TaskItemHelpers.basicParser markdown + |> Result.withDefault TaskItem.dummy untaggedAndCompleted : Settings diff --git a/tests/TagListTests.elm b/tests/TagListTests.elm index 3cc5e397..99ae3cdb 100644 --- a/tests/TagListTests.elm +++ b/tests/TagListTests.elm @@ -1,10 +1,12 @@ module TagListTests exposing (suite) import Expect +import Helpers.DecodeHelpers as DecodeTestHelpers import Parser import Tag exposing (Tag) import TagList exposing (TagList) import Test exposing (..) +import TsJson.Encode as TsEncode suite : Test @@ -15,7 +17,9 @@ suite = , containsTagMatching , containsTagMatchingOneOf , containsTagOtherThanThese + , decoder , empty + , encoder , filter , fromList , isEmpty @@ -197,6 +201,18 @@ containsTagOtherThanThese = ] +decoder : Test +decoder = + describe "decoder" + [ test "decodes from a list of strings" <| + \() -> + "[\"atag\",\"btag\"]" + |> DecodeTestHelpers.runDecoder TagList.decoder + |> .decoded + |> Expect.equal (Ok <| TagList.fromList [ "atag", "btag" ]) + ] + + empty : Test empty = describe "empty" @@ -208,6 +224,18 @@ empty = ] +encoder : Test +encoder = + describe "encoder" + [ test "encodes the list as a list of strings" <| + \() -> + TagList.fromList [ "atag", "btag" ] + |> TsEncode.runExample TagList.encoder + |> .output + |> Expect.equal "[\"atag\",\"btag\"]" + ] + + filter : Test filter = describe "filter" diff --git a/tests/TagTests.elm b/tests/TagTests.elm index 15a94508..2f630449 100644 --- a/tests/TagTests.elm +++ b/tests/TagTests.elm @@ -2,10 +2,12 @@ module TagTests exposing (suite) import Expect import Fuzz exposing (Fuzzer) +import Helpers.DecodeHelpers as DecodeTestHelpers import Helpers.UnicodeHelpers as UnicodeHelpers import Parser exposing ((|.), (|=)) import Tag import Test exposing (..) +import TsJson.Encode as TsEncode import Unicode @@ -13,6 +15,8 @@ suite : Test suite = concat [ containsInvalidCharacters + , decoder + , encoder , equals , matches , parser @@ -47,6 +51,32 @@ containsInvalidCharacters = ] +decoder : Test +decoder = + describe "decoder" + [ test "decodes a tag from a string" <| + \() -> + "\"foo\"" + |> DecodeTestHelpers.runDecoder Tag.decoder + |> .decoded + |> Result.map Tag.toString + |> Expect.equal (Ok "foo") + ] + + +encoder : Test +encoder = + describe "encoder" + [ test "encodes the tag contents as a string" <| + \() -> + "#foo" + |> Parser.run Tag.parser + |> Result.map (TsEncode.runExample Tag.encoder) + |> Result.map .output + |> Expect.equal (Ok "\"foo\"") + ] + + equals : Test equals = describe "equals" diff --git a/tests/TaskItemTests.elm b/tests/TaskItemTests.elm index d2a36658..feb0268c 100644 --- a/tests/TaskItemTests.elm +++ b/tests/TaskItemTests.elm @@ -4,14 +4,16 @@ import DataviewTaskCompletion import Date import Expect import Filter +import Helpers.DecodeHelpers as DecodeTestHelpers import Helpers.FilterHelpers as FilterHelpers import Helpers.TaskHelpers as TaskHelpers import Helpers.TaskItemHelpers as TaskItemHelpers import Parser exposing ((|=)) import TagList -import TaskItem exposing (Completion(..)) +import TaskItem exposing (Completion(..), TaskItem) import Test exposing (..) import Time +import TsJson.Encode as TsEncode suite : Test @@ -19,11 +21,14 @@ suite = concat [ allSubtasksWithMatchingTagCompleted , blockLink + , compare , completedPosix , completion , containsId + , decoder , descendantTasks , due + , encoder , filePath , hasTags , hasThisTagBasic @@ -37,7 +42,8 @@ suite = , isDated , lineNumber , notes - , originalText + , originalBlock + , originalLine , parsing , removeMatchingTags , removeTags @@ -123,6 +129,212 @@ blockLink = ] +compare : Test +compare = + describe "compare" + [ test "returns Identical for two basic taskItems" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 0 "- [ ] foo" + in + taskItemPlus "a" 0 "- [ ] foo" + |> TaskItem.compare other + |> Expect.equal TaskItem.Identical + , test "returns Identical for two taskItems with notes" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 0 "- [ ] foo\n some notes\n" + in + taskItemPlus "a" 0 "- [ ] foo\n some notes\n" + |> TaskItem.compare other + |> Expect.equal TaskItem.Identical + , test "returns Identical for two taskItems with notes and subtasks" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 0 "- [ ] foo\n some notes\n - [ ] bar\n" + in + taskItemPlus "a" 0 "- [ ] foo\n some notes\n - [ ] bar\n" + |> TaskItem.compare other + |> Expect.equal TaskItem.Identical + , test "returns Updated if a subtask has been added" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 0 "- [ ] foo\n - [ ] bar\n" + in + taskItemPlus "a" 0 "- [ ] foo" + |> TaskItem.compare other + |> Expect.equal TaskItem.Updated + , test "returns Updated if a subtask and notes have been added" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 0 "- [ ] foo\n some notes\n - [ ] bar\n" + in + taskItemPlus "a" 0 "- [ ] foo" + |> TaskItem.compare other + |> Expect.equal TaskItem.Updated + , test "returns Updated if the subtask and notes have been edited" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 0 "- [ ] foo\n some edited notes\n - [ ] bar-foo\n" + in + taskItemPlus "a" 0 "- [ ] foo\n some notes\n - [ ] bar\n" + |> TaskItem.compare other + |> Expect.equal TaskItem.Updated + , test "returns Updated if the subtask and notes have been deleted" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 0 "- [ ] foo" + in + taskItemPlus "a" 0 "- [ ] foo\n some edited notes\n - [ ] bar-foo\n" + |> TaskItem.compare other + |> Expect.equal TaskItem.Updated + , test "returns Updated if tags have been added" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 0 "- [ ] foo #bar @due(2021-01-01)" + in + taskItemPlus "a" 0 "- [ ] foo" + |> TaskItem.compare other + |> Expect.equal TaskItem.Updated + , test "returns Updated if the title 'is similar'" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 0 "- [ ] this is something I need to do" + in + taskItemPlus "a" 0 "- [ ] this is something" + |> TaskItem.compare other + |> Expect.equal TaskItem.Updated + , test "returns Moved if a basic task block is the same but it is in a different location" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 1 "- [ ] foo" + in + taskItemPlus "a" 0 "- [ ] foo" + |> TaskItem.compare other + |> Expect.equal TaskItem.Moved + , test "returns Moved if a task block with notes and subtasks is the same but it is in a different location" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 1 "- [ ] foo\n some notes\n - [ ] bar\n" + in + taskItemPlus "a" 0 "- [ ] foo\n some notes\n - [ ] bar\n" + |> TaskItem.compare other + |> Expect.equal TaskItem.Moved + , test "returns Moved if a task block with notes is the same but it is in a different location" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 1 "- [ ] foo\n some notes\n" + in + taskItemPlus "a" 0 "- [ ] foo\n some notes\n" + |> TaskItem.compare other + |> Expect.equal TaskItem.Moved + , test "returns MovedAndUpdated if a subtask has been added and it has been moved" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 1 "- [ ] foo\n - [ ] bar\n" + in + taskItemPlus "a" 0 "- [ ] foo" + |> TaskItem.compare other + |> Expect.equal TaskItem.MovedAndUpdated + , test "returns MovedAndUpdated if a subtask and notes have been added and it has been moved" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 1 "- [ ] foo\n some notes\n - [ ] bar\n" + in + taskItemPlus "a" 0 "- [ ] foo" + |> TaskItem.compare other + |> Expect.equal TaskItem.MovedAndUpdated + , test "returns MovedAndUpdated if the subtask and notes have been edited and it has been moved" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 1 "- [ ] foo\n some edited notes\n - [ ] bar-foo\n" + in + taskItemPlus "a" 0 "- [ ] foo\n some notes\n - [ ] bar\n" + |> TaskItem.compare other + |> Expect.equal TaskItem.MovedAndUpdated + , test "returns MovedAndUpdated if the subtask and notes have been deleted and it has been moved" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 1 "- [ ] foo" + in + taskItemPlus "a" 0 "- [ ] foo\n some edited notes\n - [ ] bar-foo\n" + |> TaskItem.compare other + |> Expect.equal TaskItem.MovedAndUpdated + , test "returns MovedAndUpdated if tags have been added and it has been moved" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 1 "- [ ] foo #bar @due(2021-01-01)" + in + taskItemPlus "a" 0 "- [ ] foo" + |> TaskItem.compare other + |> Expect.equal TaskItem.MovedAndUpdated + , test "returns Updated if the title 'is similar' and it has been moved" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 1 "- [ ] this is something xxxx" + in + taskItemPlus "a" 0 "- [ ] this is something" + |> TaskItem.compare other + |> Expect.equal TaskItem.MovedAndUpdated + , test "is more fussy about similarity when moved and edited than when just edited" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 1 "- [ ] this is something I need to do" + in + taskItemPlus "a" 0 "- [ ] this is something" + |> TaskItem.compare other + |> Expect.equal TaskItem.Different + , test "returns Different if the top level title is different" <| + \() -> + let + other : TaskItem + other = + taskItemPlus "a" 0 "- [ ] foo" + in + taskItemPlus "a" 0 "- [ ] bar" + |> TaskItem.compare other + |> Expect.equal TaskItem.Different + ] + + completedPosix : Test completedPosix = describe "completedPosix" @@ -315,6 +527,47 @@ containsId = ] +decoder : Test +decoder = + describe "decoder" + [ test "decodes a basic TaskItem" <| + \() -> + """{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] foo","originalLine":"- [ ] foo","tags":[],"title":["foo"]},"subFields":[]}""" + |> DecodeTestHelpers.runDecoder TaskItem.decoder + |> .decoded + |> Result.map TaskItem.title + |> Expect.equal (Ok <| "foo") + , test "decodes a well decorated TaskItem" <| + \() -> + let + taskItem : TaskItem + taskItem = + "- [x] foo #bar @autocomplete(true) [due:: 2024-01-01]" + |> Parser.run (TaskItem.parser DataviewTaskCompletion.NoCompletion "" Nothing TagList.empty 0) + |> Result.withDefault TaskItem.dummy + in + """{"fields":{"autoComplete":{"tag":"TrueSpecified"},"completion":{"tag":"Completed"},"contents":[{"tag":"DueTag","data":{"tag":"SetToDate","date":738886}},{"tag":"AutoCompleteTag","data":{"tag":"TrueSpecified"}},{"tag":"ObsidianTag","data":"bar"},{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"SetToDate","date":738886},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [x] foo #bar @autocomplete(true) [due:: 2024-01-01]","originalLine":"- [x] foo #bar @autocomplete(true) [due:: 2024-01-01]","tags":["bar"],"title":["foo"]},"subFields":[]}""" + |> DecodeTestHelpers.runDecoder TaskItem.decoder + |> .decoded + |> Result.toMaybe + |> Expect.equal (Just taskItem) + , test "decodes a TaskItem with subtasks" <| + \() -> + let + taskItem : TaskItem + taskItem = + "- [ ] foo\n - [ ] bar" + |> Parser.run (TaskItem.parser DataviewTaskCompletion.NoCompletion "" Nothing TagList.empty 0) + |> Result.withDefault TaskItem.dummy + in + """{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] foo\\n - [ ] bar","originalLine":"- [ ] foo","tags":[],"title":["foo"]},"subFields":[{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"bar"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":2,"notes":"","originalBlock":"","originalLine":" - [ ] bar","tags":[],"title":["bar"]}]}""" + |> DecodeTestHelpers.runDecoder TaskItem.decoder + |> .decoded + |> Result.toMaybe + |> Expect.equal (Just taskItem) + ] + + descendantTasks : Test descendantTasks = describe "descendantTasks" @@ -499,6 +752,33 @@ due = ] +encoder : Test +encoder = + describe "encoder" + [ test "encodes a basic TaskItem" <| + \() -> + "- [ ] foo" + |> Parser.run (TaskItem.parser DataviewTaskCompletion.NoCompletion "" Nothing TagList.empty 0) + |> Result.map (TsEncode.runExample TaskItem.encoder) + |> Result.map .output + |> Expect.equal (Ok """{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] foo","originalLine":"- [ ] foo","tags":[],"title":["foo"]},"subFields":[]}""") + , test "encodes a well decorated TaskItem" <| + \() -> + "- [x] foo #bar @autocomplete(true) [due:: 2024-01-01]" + |> Parser.run (TaskItem.parser DataviewTaskCompletion.NoCompletion "" Nothing TagList.empty 0) + |> Result.map (TsEncode.runExample TaskItem.encoder) + |> Result.map .output + |> Expect.equal (Ok """{"fields":{"autoComplete":{"tag":"TrueSpecified"},"completion":{"tag":"Completed"},"contents":[{"tag":"DueTag","data":{"tag":"SetToDate","date":738886}},{"tag":"AutoCompleteTag","data":{"tag":"TrueSpecified"}},{"tag":"ObsidianTag","data":"bar"},{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"SetToDate","date":738886},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [x] foo #bar @autocomplete(true) [due:: 2024-01-01]","originalLine":"- [x] foo #bar @autocomplete(true) [due:: 2024-01-01]","tags":["bar"],"title":["foo"]},"subFields":[]}""") + , test "encodes a TaskItem with subtasks" <| + \() -> + "- [ ] foo\n - [ ] bar" + |> Parser.run (TaskItem.parser DataviewTaskCompletion.NoCompletion "" Nothing TagList.empty 0) + |> Result.map (TsEncode.runExample TaskItem.encoder) + |> Result.map .output + |> Expect.equal (Ok """{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] foo\\n - [ ] bar","originalLine":"- [ ] foo","tags":[],"title":["foo"]},"subFields":[{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"bar"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":2,"notes":"","originalBlock":"","originalLine":" - [ ] bar","tags":[],"title":["bar"]}]}""") + ] + + filePath : Test filePath = describe "filePath" @@ -974,27 +1254,51 @@ notes = ] -originalText : Test -originalText = - describe "originalText" +originalBlock : Test +originalBlock = + describe "originalBlock" + [ test "retains the original line text for a single line task" <| + \() -> + "- [X] the @due(2019-12-30) task @completed(2020-01-01) title " + |> Parser.run TaskItemHelpers.basicParser + |> Result.map TaskItem.originalBlock + |> Expect.equal (Ok "- [X] the @due(2019-12-30) task @completed(2020-01-01) title") + , test "retains the original line including any indented notes" <| + \() -> + "- [ ] foo\n some notes\n" + |> Parser.run TaskItemHelpers.basicParser + |> Result.map TaskItem.originalBlock + |> Expect.equal (Ok "- [ ] foo\n some notes") + , test "retains the original line including any subtasks" <| + \() -> + "- [ ] foo\n some notes\n - [ ] a subtask\n\n more notes\n - [ ]invalid subtask\n" + |> Parser.run TaskItemHelpers.basicParser + |> Result.map TaskItem.originalBlock + |> Expect.equal (Ok "- [ ] foo\n some notes\n - [ ] a subtask\n\n more notes\n - [ ]invalid subtask") + ] + + +originalLine : Test +originalLine = + describe "originalLine" [ test "retains the original line text" <| \() -> "- [X] the @due(2019-12-30) task @completed(2020-01-01) title " |> Parser.run TaskItemHelpers.basicParser - |> Result.map TaskItem.originalText + |> Result.map TaskItem.originalLine |> Expect.equal (Ok "- [X] the @due(2019-12-30) task @completed(2020-01-01) title ") , test "retains the original line text even with a '*' list marker" <| \() -> "* [X] the @due(2019-12-30) task @completed(2020-01-01) title " |> Parser.run TaskItemHelpers.basicParser - |> Result.map TaskItem.originalText + |> Result.map TaskItem.originalLine |> Expect.equal (Ok "* [X] the @due(2019-12-30) task @completed(2020-01-01) title ") , test "retains leading whitepace for the original line text for descendantTasks" <| \() -> "- [X] task\n \t - [ ] sub-task" |> Parser.run TaskItemHelpers.basicParser |> Result.map TaskItem.descendantTasks - |> Result.map (List.map TaskItem.originalText) + |> Result.map (List.map TaskItem.originalLine) |> Expect.equal (Ok [ " \t - [ ] sub-task" ]) ] @@ -1399,3 +1703,13 @@ updateFilePath = |> TaskItem.filePath |> Expect.equal "old/path" ] + + + +-- HELPERS + + +taskItemPlus : String -> Int -> String -> TaskItem +taskItemPlus file offset markdown = + Parser.run (TaskItem.parser DataviewTaskCompletion.NoCompletion file Nothing TagList.empty offset) markdown + |> Result.withDefault TaskItem.dummy diff --git a/tests/TaskListTests.elm b/tests/TaskListTests.elm index 6134815e..e595247f 100644 --- a/tests/TaskListTests.elm +++ b/tests/TaskListTests.elm @@ -2,14 +2,17 @@ module TaskListTests exposing (suite) import DataviewTaskCompletion import Expect +import Helpers.DecodeHelpers as DecodeTestHelpers import Helpers.TaskHelpers as TaskHelpers import Helpers.TaskItemHelpers as TaskItemHelpers import Helpers.TaskListHelpers as TaskListHelpers +import MarkdownFile exposing (MarkdownFile) import Parser import TagList import TaskItem exposing (TaskItem) -import TaskList +import TaskList exposing (TaskList) import Test exposing (..) +import TsJson.Encode as TsEncode suite : Test @@ -17,14 +20,17 @@ suite = concat [ add , combine + , decoder + , encoder , filter + , fromList + , fromMarkdown , map - , parsing - , replaceForFile - , removeForFile + , markdownDiffs + , replaceTaskItems , taskContainingId , taskFromId - , tasks + , toList ] @@ -65,16 +71,62 @@ combine = ] +decoder : Test +decoder = + describe "decoder" + [ test "decodes an empty TaskList" <| + \() -> + "[]" + |> DecodeTestHelpers.runDecoder TaskList.decoder + |> .decoded + |> Expect.equal (Ok TaskList.empty) + , test "decodes a TaskList containing tasks" <| + \() -> + let + taskList : TaskList + taskList = + TaskList.empty + |> TaskList.add (taskItem "- [ ] foo") + |> TaskList.add (taskItem "- [ ] bar") + in + """[{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"bar"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] bar","originalLine":"- [ ] bar","tags":[],"title":["bar"]},"subFields":[]},{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] foo","originalLine":"- [ ] foo","tags":[],"title":["foo"]},"subFields":[]}]""" + |> DecodeTestHelpers.runDecoder TaskList.decoder + |> .decoded + |> Expect.equal (Ok taskList) + ] + + +encoder : Test +encoder = + describe "encoder" + [ test "encodes an empty TaskList" <| + \() -> + TaskList.empty + |> TsEncode.runExample TaskList.encoder + |> .output + |> Expect.equal """[]""" + , test "encodes an TaskList containing tasks" <| + \() -> + TaskList.empty + |> TaskList.add (taskItem "- [ ] foo") + |> TaskList.add (taskItem "- [ ] bar") + |> TsEncode.runExample TaskList.encoder + |> .output + |> Expect.equal """[{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"bar"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] bar","originalLine":"- [ ] bar","tags":[],"title":["bar"]},"subFields":[]},{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] foo","originalLine":"- [ ] foo","tags":[],"title":["foo"]},"subFields":[]}]""" + ] + + filter : Test filter = describe "filter" [ test "returns an empty TaskList if given an one" <| \() -> "" - |> Parser.run TaskListHelpers.basicParser - |> Result.map (TaskList.filter (always True)) - |> Result.map TaskList.taskTitles - |> Expect.equal (Ok []) + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.filter (always True) + |> TaskList.taskTitles + |> Expect.equal [] , test "filters a TaskList based on a TaskItem property" <| \() -> """- [ ] foo @@ -82,51 +134,45 @@ filter = - [X] baz #tag2 - [X] boo """ - |> Parser.run TaskListHelpers.basicParser - |> Result.map (TaskList.filter TaskItem.hasTags) - |> Result.map TaskList.taskTitles - |> Expect.equal (Ok [ "bar", "baz" ]) + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.filter TaskItem.hasTags + |> TaskList.taskTitles + |> Expect.equal [ "bar", "baz" ] ] -map : Test -map = - describe "map" - [ test "returns an empty TaskList if given an one" <| +fromList : Test +fromList = + describe "fromList" + [ test "returns an empty TaskList if given an empty list" <| \() -> - "" - |> Parser.run TaskListHelpers.basicParser - |> Result.map (TaskList.map identity) - |> Result.map TaskList.taskTitles - |> Expect.equal (Ok []) - , test "maps the contents of a TaskList throgh a function" <| + [] + |> TaskList.fromList + |> Expect.equal TaskList.empty + , test "returns a TaskList containing the items in the list" <| \() -> - """- [ ] foo -- [x] bar #tag1 -""" - |> Parser.run (TaskList.parser DataviewTaskCompletion.NoCompletion "old/path" Nothing TagList.empty 0) - |> Result.map (TaskList.map <| TaskItem.updateFilePath "old/path" "new/path") - |> Result.map TaskList.topLevelTasks - |> Result.map (List.map TaskItem.filePath) - |> Expect.equal (Ok [ "new/path", "new/path" ]) + [ taskItem "- [ ] foo", taskItem "- [ ] bar" ] + |> TaskList.fromList + |> TaskList.taskTitles + |> Expect.equal [ "foo", "bar" ] ] -parsing : Test -parsing = - describe "todo parsing" +fromMarkdown : Test +fromMarkdown = + describe "todo fromMarkdown" [ test "parses an empty file" <| \() -> "" - |> Parser.run TaskListHelpers.basicParser - |> Result.withDefault TaskList.empty - |> TaskList.taskTitles - |> Expect.equal [] + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> Expect.equal TaskList.empty , test "parses a single incomplete TaskList item" <| \() -> "- [ ] foo" - |> Parser.run TaskListHelpers.basicParser - |> Result.withDefault TaskList.empty + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion |> TaskList.taskTitles |> Expect.equal [ "foo" ] , test "parses a contiguous block of TaskList items" <| @@ -135,8 +181,8 @@ parsing = - [x] bar - [X] baz """ - |> Parser.run TaskListHelpers.basicParser - |> Result.withDefault TaskList.empty + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion |> TaskList.taskTitles |> Expect.equal [ "foo", "bar", "baz" ] , test "parses non contiguous TaskList items" <| @@ -149,8 +195,8 @@ parsing = - [X] baz """ - |> Parser.run TaskListHelpers.basicParser - |> Result.withDefault TaskList.empty + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion |> TaskList.taskTitles |> Expect.equal [ "foo", "bar", "baz" ] , test "parses TaskList items with non-tasks interspersed" <| @@ -164,8 +210,8 @@ not a task - [X] baz """ - |> Parser.run TaskListHelpers.basicParser - |> Result.withDefault TaskList.empty + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion |> TaskList.taskTitles |> Expect.equal [ "foo", "bar", "baz" ] , test "ignores indented tasks" <| @@ -180,8 +226,8 @@ not a task - [ ] a subtask """ - |> Parser.run TaskListHelpers.basicParser - |> Result.withDefault TaskList.empty + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion |> TaskList.taskTitles |> Expect.equal [ "foo", "bar", "baz" ] , test "parses tasks when the first line of the file is blank" <| @@ -191,8 +237,8 @@ not a task - [x] bar - [X] baz """ - |> Parser.run TaskListHelpers.basicParser - |> Result.withDefault TaskList.empty + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion |> TaskList.taskTitles |> Expect.equal [ "foo", "bar", "baz" ] , test "parses tasks ignoring any that don't have a title" <| @@ -203,22 +249,22 @@ not a task - [x] bar - [X] baz """ - |> Parser.run TaskListHelpers.basicParser - |> Result.withDefault TaskList.empty + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion |> TaskList.taskTitles |> Expect.equal [ "foo", "bar", "baz" ] , test "parses tasks when the last line is a task and has NO line ending" <| \() -> "- [ ] foo\n- [x] bar" - |> Parser.run TaskListHelpers.basicParser - |> Result.withDefault TaskList.empty + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion |> TaskList.taskTitles |> Expect.equal [ "foo", "bar" ] , test "parses tasks when the last line is a non-task and has a line ending" <| \() -> "- [ ] foo\n- [x] bar\n\n## Log\n" - |> Parser.run TaskListHelpers.basicParser - |> Result.withDefault TaskList.empty + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion |> TaskList.taskTitles |> Expect.equal [ "foo", "bar" ] , test "parses ids consiting of the filePath and line number" <| @@ -231,18 +277,19 @@ not a task - [X] baz """ - |> Parser.run (TaskList.parser DataviewTaskCompletion.NoCompletion "file_a" Nothing TagList.empty 0) - |> Result.withDefault TaskList.empty + |> basicMarkdown "file_a" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion |> TaskList.taskIds |> Expect.equal [ "4275677999:1", "4275677999:4", "4275677999:6" ] - , test "adds frontmatter tags to the tasks" <| + , test "adds frontmatter tags to all the tasks" <| \() -> """- [ ] foo - [x] bar """ - |> Parser.run (TaskList.parser DataviewTaskCompletion.NoCompletion "file_a" Nothing (TagList.fromList [ "fm_tag1", "fm_tag2" ]) 0) - |> Result.withDefault TaskList.empty - |> TaskList.tasks + |> basicMarkdown "file_a" + |> (\md -> { md | frontMatterTags = TagList.fromList [ "fm_tag1", "fm_tag2" ] }) + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.toList |> List.map TaskItem.tags |> Expect.equal [ TagList.fromList [ "fm_tag1", "fm_tag2" ] @@ -251,55 +298,185 @@ not a task ] -replaceForFile : Test -replaceForFile = - describe "replacing tasks from a chosen file" - [ test "adds the tasks if the TaskList is empty" <| +markdownDiffs : Test +markdownDiffs = + describe "markdownDiffs" + [ test "returns an empty diff if both the original an updated files are empty" <| \() -> - TaskList.empty - |> TaskList.replaceForFile "ignored" - (TaskListHelpers.taskListFromFile "file a") - |> TaskList.taskTitles - |> List.sort - |> Expect.equal (List.sort [ "c1", "c2" ]) - , test "adds the tasks if the list doesn't contain tasks from the file" <| + let + updatedMarkdown : MarkdownFile + updatedMarkdown = + basicMarkdown "a" "" + in + "" + |> basicMarkdown "a" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.markdownDiffs DataviewTaskCompletion.NoCompletion updatedMarkdown + |> Expect.equal { toAdd = [], toDelete = [] } + , test "returns an empty diff if both the original an updated files are the same" <| + \() -> + let + updatedMarkdown : MarkdownFile + updatedMarkdown = + basicMarkdown "a" "- [ ] foo" + in + "- [ ] foo" + |> basicMarkdown "a" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.markdownDiffs DataviewTaskCompletion.NoCompletion updatedMarkdown + |> Expect.equal { toAdd = [], toDelete = [] } + , test "returns a an empty diff if non-task line is edited" <| + \() -> + let + updatedMarkdown : MarkdownFile + updatedMarkdown = + basicMarkdown "a" "# title\n- [ ] foo" + in + "# LONGER title\n- [ ] foo" + |> basicMarkdown "a" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.markdownDiffs DataviewTaskCompletion.NoCompletion updatedMarkdown + |> Expect.equal { toAdd = [], toDelete = [] } + , test "returns a task to add if one has been added" <| + \() -> + let + updatedMarkdown : MarkdownFile + updatedMarkdown = + basicMarkdown "a" "# title\n- [ ] foo\n- [ ] bar" + in + "# title\n- [ ] foo" + |> basicMarkdown "a" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.markdownDiffs DataviewTaskCompletion.NoCompletion updatedMarkdown + |> Expect.equal + { toAdd = [ taskItemPlus "a" 2 "- [ ] bar" ] + , toDelete = [] + } + , test "returns a task to remove if one has been removed" <| + \() -> + let + updatedMarkdown : MarkdownFile + updatedMarkdown = + basicMarkdown "a" "# title\n- [ ] foo" + in + "# title\n- [ ] foo\n- [ ] bar" + |> basicMarkdown "a" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.markdownDiffs DataviewTaskCompletion.NoCompletion updatedMarkdown + |> Expect.equal + { toAdd = [] + , toDelete = [ taskItemPlus "a" 2 "- [ ] bar" ] + } + , test "returns tasks to remove and add if one has been edited" <| + \() -> + let + updatedMarkdown : MarkdownFile + updatedMarkdown = + basicMarkdown "a" "# title\n- [ ] xxx" + in + "# title\n- [ ] foo" + |> basicMarkdown "a" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.markdownDiffs DataviewTaskCompletion.NoCompletion updatedMarkdown + |> Expect.equal + { toAdd = [ taskItemPlus "a" 1 "- [ ] xxx" ] + , toDelete = [ taskItemPlus "a" 1 "- [ ] foo" ] + } + , test "returns deletes and adds if it has had a subtask added" <| + \() -> + let + updatedMarkdown : MarkdownFile + updatedMarkdown = + basicMarkdown "a" "# title\n- [ ] foo\n- [ ] bar\n - [ ] baz" + in + "# title\n- [ ] foo\n- [ ] bar" + |> basicMarkdown "a" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.markdownDiffs DataviewTaskCompletion.NoCompletion updatedMarkdown + |> Expect.equal + { toAdd = [ taskItemPlus "a" 2 "- [ ] bar\n - [ ] baz" ] + , toDelete = [ taskItemPlus "a" 2 "- [ ] bar" ] + } + , test "returns deletes and adds if it has had a subtask edited" <| + \() -> + let + updatedMarkdown : MarkdownFile + updatedMarkdown = + basicMarkdown "a" "# title\n- [ ] foo\n- [ ] bar\n - [ ] bazzer" + in + "# title\n- [ ] foo\n- [ ] bar\n - [ ] baz" + |> basicMarkdown "a" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.markdownDiffs DataviewTaskCompletion.NoCompletion updatedMarkdown + |> Expect.equal + { toAdd = [ taskItemPlus "a" 2 "- [ ] bar\n - [ ] bazzer" ] + , toDelete = [ taskItemPlus "a" 2 "- [ ] bar\n - [ ] baz" ] + } + , test "returns deletes and adds if it has had notes deleted" <| + \() -> + let + updatedMarkdown : MarkdownFile + updatedMarkdown = + basicMarkdown "a" "# title\n- [ ] foo" + in + "# title\n- [ ] foo\n some notes\n" + |> basicMarkdown "a" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.markdownDiffs DataviewTaskCompletion.NoCompletion updatedMarkdown + |> Expect.equal + { toAdd = [ taskItemPlus "a" 1 "- [ ] foo" ] + , toDelete = [ taskItemPlus "a" 1 "- [ ] foo\n some notes\n" ] + } + ] + + +map : Test +map = + describe "map" + [ test "returns an empty TaskList if given an one" <| \() -> - TaskListHelpers.taskListFromFileG - |> TaskList.replaceForFile "ignored" - (TaskListHelpers.taskListFromFile "file a") + "" + |> basicMarkdown "" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> TaskList.map identity |> TaskList.taskTitles - |> List.sort - |> Expect.equal (List.sort [ "g1", "g2", "c1", "c2" ]) - , test "replaces tasks from the file" <| + |> Expect.equal [] + , test "maps the contents of a TaskList throgh a function" <| \() -> - TaskListHelpers.taskListFromFileG - |> TaskList.append (TaskListHelpers.taskListFromFile "file a") - |> TaskList.replaceForFile "file a" - (TaskListHelpers.taskListFromNewFile "could be another file") - |> TaskList.taskTitles - |> List.sort - |> Expect.equal (List.sort [ "n1", "n2", "g1", "g2" ]) + """- [ ] foo +- [x] bar #tag1 +""" + |> basicMarkdown "old/path" + |> TaskList.fromMarkdown DataviewTaskCompletion.NoCompletion + |> (TaskList.map <| TaskItem.updateFilePath "old/path" "new/path") + |> TaskList.topLevelTasks + |> List.map TaskItem.filePath + |> Expect.equal [ "new/path", "new/path" ] ] -removeForFile : Test -removeForFile = - describe "removing tasks from a chosen file" - [ test "has no effect if the TaskList is empty" <| +replaceTaskItems : Test +replaceTaskItems = + describe "replaceTaskItems" + [ test "does nothing if there are no replacement details" <| \() -> - TaskList.empty - |> TaskList.removeForFile "a file" + TaskListHelpers.parsedTasks ( "a", Nothing, """ +- [ ] foo +- [x] bar +""" ) + |> TaskList.replaceTaskItems [] |> TaskList.taskTitles - |> List.sort - |> Expect.equal (List.sort []) - , test "remove tasks from the file" <| + |> Expect.equal [ "foo", "bar" ] + , test "replaces TaskItems if the id matches" <| \() -> - TaskListHelpers.taskListFromFileG - |> TaskList.append (TaskListHelpers.taskListFromFile "file a") - |> TaskList.removeForFile "file a" + TaskListHelpers.parsedTasks ( "a", Nothing, """ +- [ ] foo +- [x] bar +""" ) + |> TaskList.replaceTaskItems [ ( "3826002220:3", taskItem "- [ ] baz" ) ] |> TaskList.taskTitles |> List.sort - |> Expect.equal (List.sort [ "g1", "g2" ]) + |> Expect.equal (List.sort [ "foo", "baz" ]) ] @@ -377,13 +554,13 @@ taskFromId = ] -tasks : Test -tasks = +toList : Test +toList = describe "tasks" [ test "returns an empty list if there are no tasks" <| \() -> TaskListHelpers.parsedTasks ( "a", Nothing, "" ) - |> TaskList.tasks + |> TaskList.toList |> Expect.equal [] , test "returns a list of all tasks and subtasks" <| \() -> @@ -391,7 +568,7 @@ tasks = - [ ] g1 - [x] subtask complete """ ) - |> TaskList.tasks + |> TaskList.toList |> List.map TaskItem.title |> Expect.equal [ "g1", "subtask complete" ] ] @@ -401,6 +578,22 @@ tasks = -- HELPERS +basicMarkdown : String -> String -> MarkdownFile +basicMarkdown path body = + { filePath = path + , fileDate = Nothing + , frontMatterTags = TagList.empty + , bodyOffset = 0 + , body = body + } + + +taskItemPlus : String -> Int -> String -> TaskItem +taskItemPlus file offset markdown = + Parser.run (TaskItem.parser DataviewTaskCompletion.NoCompletion file Nothing TagList.empty offset) markdown + |> Result.withDefault TaskItem.dummy + + taskItem : String -> TaskItem taskItem markdown = Parser.run TaskItemHelpers.basicParser markdown diff --git a/tests/Worker/InteropDefinitionsTests.elm b/tests/Worker/InteropDefinitionsTests.elm new file mode 100644 index 00000000..48a8c678 --- /dev/null +++ b/tests/Worker/InteropDefinitionsTests.elm @@ -0,0 +1,169 @@ +module Worker.InteropDefinitionsTests exposing (suite) + +import Expect +import Helpers.DecodeHelpers as DecodeHelpers +import Helpers.TaskItemHelpers exposing (safeTaskItem) +import TagList +import TaskList +import Test exposing (..) +import TsJson.Encode as TsEncode +import Worker.InteropDefinitions as InteropDefinitions exposing (interop) + + +suite : Test +suite = + concat + [ fromElmTests + , toElmTests + ] + + +fromElmTests : Test +fromElmTests = + describe "interop.fromElm (encoding)" + [ test "encodes AllTasksLoaded" <| + \() -> + InteropDefinitions.AllTasksLoaded + |> TsEncode.runExample interop.fromElm + |> .output + |> Expect.equal """{"tag":"allTasksLoaded"}""" + , test "encodes AllTaskItems with an empty TaskList" <| + \() -> + TaskList.empty + |> InteropDefinitions.AllTaskItems + |> TsEncode.runExample interop.fromElm + |> .output + |> Expect.equal """{"tag":"allTaskItems","data":[]}""" + , test "encodes AllTaskItems with an non-empty TaskList" <| + \() -> + [ safeTaskItem "- [ ] foo", safeTaskItem "- [ ] bar" ] + |> TaskList.fromList + |> InteropDefinitions.AllTaskItems + |> TsEncode.runExample interop.fromElm + |> .output + |> Expect.equal """{"tag":"allTaskItems","data":[{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] foo","originalLine":"- [ ] foo","tags":[],"title":["foo"]},"subFields":[]},{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"bar"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] bar","originalLine":"- [ ] bar","tags":[],"title":["bar"]},"subFields":[]}]}""" + , test "encodes TasksAdded with an empty TaskList" <| + \() -> + TaskList.empty + |> InteropDefinitions.TasksAdded + |> TsEncode.runExample interop.fromElm + |> .output + |> Expect.equal """{"tag":"tasksAdded","data":[]}""" + , test "encodes TasksAdded with an non-empty TaskList" <| + \() -> + [ safeTaskItem "- [ ] foo", safeTaskItem "- [ ] bar" ] + |> TaskList.fromList + |> InteropDefinitions.TasksAdded + |> TsEncode.runExample interop.fromElm + |> .output + |> Expect.equal """{"tag":"tasksAdded","data":[{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] foo","originalLine":"- [ ] foo","tags":[],"title":["foo"]},"subFields":[]},{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"bar"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] bar","originalLine":"- [ ] bar","tags":[],"title":["bar"]},"subFields":[]}]}""" + , test "encodes TasksDeleted with no TaskItems" <| + \() -> + [] + |> InteropDefinitions.TasksDeleted + |> TsEncode.runExample interop.fromElm + |> .output + |> Expect.equal """{"tag":"tasksDeleted","data":[]}""" + , test "encodes TasksDeleted with some TaskItems" <| + \() -> + [ safeTaskItem "- [ ] foo", safeTaskItem "- [ ] bar" ] + |> InteropDefinitions.TasksDeleted + |> TsEncode.runExample interop.fromElm + |> .output + |> Expect.equal """{"tag":"tasksDeleted","data":["2166136261:1","2166136261:1"]}""" + , test "encodes TasksDeletedAndAdded with no TaskItems" <| + \() -> + ( [], [] ) + |> InteropDefinitions.TasksDeletedAndAdded + |> TsEncode.runExample interop.fromElm + |> .output + |> Expect.equal """{"tag":"tasksDeletedAndAdded","data":[[],[]]}""" + , test "encodes TasksDeletedAndAdded with some TaskItems" <| + \() -> + ( [ "taskId:1", "taskId:2" ], [ safeTaskItem "- [ ] baz" ] ) + |> InteropDefinitions.TasksDeletedAndAdded + |> TsEncode.runExample interop.fromElm + |> .output + |> Expect.equal """{"tag":"tasksDeletedAndAdded","data":[["taskId:1","taskId:2"],[{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"baz"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] baz","originalLine":"- [ ] baz","tags":[],"title":["baz"]},"subFields":[]}]]}""" + , test "encodes TasksUpdated with no TaskItems" <| + \() -> + [] + |> InteropDefinitions.TasksUpdated + |> TsEncode.runExample interop.fromElm + |> .output + |> Expect.equal """{"tag":"tasksUpdated","data":[]}""" + , test "encodes TasksUpdated with some TaskItems" <| + \() -> + [ ( "foo", safeTaskItem "- [ ] foo" ), ( "bar", safeTaskItem "- [ ] bar" ) ] + |> InteropDefinitions.TasksUpdated + |> TsEncode.runExample interop.fromElm + |> .output + |> Expect.equal """{"tag":"tasksUpdated","data":[["foo",{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"foo"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] foo","originalLine":"- [ ] foo","tags":[],"title":["foo"]},"subFields":[]}],["bar",{"fields":{"autoComplete":{"tag":"NotSpecifed"},"completion":{"tag":"Incomplete"},"contents":[{"tag":"Word","data":"bar"}],"dueFile":null,"dueTag":{"tag":"NotSet"},"filePath":"","lineNumber":1,"notes":"","originalBlock":"- [ ] bar","originalLine":"- [ ] bar","tags":[],"title":["bar"]},"subFields":[]}]]}""" + ] + + +toElmTests : Test +toElmTests = + describe "interop.toElm (decoding)" + [ test "decodes allMarkdownLoaded" <| + \() -> + """{"tag":"allMarkdownLoaded","data":{}}""" + |> DecodeHelpers.runDecoder interop.toElm + |> .decoded + |> Expect.equal (Ok <| InteropDefinitions.AllMarkdownLoaded) + , test "decodes viewInitialized" <| + \() -> + """{"tag":"viewInitialized"}""" + |> DecodeHelpers.runDecoder interop.toElm + |> .decoded + |> Expect.equal (Ok InteropDefinitions.ViewInitialized) + , test "decodes fileAdded data" <| + \() -> + """{"tag":"fileAdded","data":{"filePath":"a path","fileDate":"a date","fileContents":"---\\ntags: [ a_tag ]\\n---\\nsome contents"}}""" + |> DecodeHelpers.runDecoder interop.toElm + |> .decoded + |> Expect.equal + (Ok <| + InteropDefinitions.FileAdded + { filePath = "a path" + , fileDate = Just "a date" + , frontMatterTags = TagList.fromList [ "a_tag" ] + , bodyOffset = 3 + , body = "some contents" + } + ) + , test "decodes fileDeleted data" <| + \() -> + """{"tag":"fileDeleted","data":"a path"}""" + |> DecodeHelpers.runDecoder interop.toElm + |> .decoded + |> Expect.equal (Ok <| InteropDefinitions.FileDeleted "a path") + , test "decodes fileModified data" <| + \() -> + """{"tag":"fileModified","data":{"filePath":"a path","fileDate":"a date","frontMatterTags":["a_tag"],"fileContents":"---\\ntags: [ a_tag ]\\n---\\nsome contents"}}""" + |> DecodeHelpers.runDecoder interop.toElm + |> .decoded + |> Expect.equal + (Ok <| + InteropDefinitions.FileModified + { filePath = "a path" + , fileDate = Just "a date" + , frontMatterTags = TagList.fromList [ "a_tag" ] + , bodyOffset = 3 + , body = "some contents" + } + ) + , test "decodes fileRenamed data" <| + \() -> + """{"tag":"fileRenamed","data":{"oldPath":"the old path","newPath":"the new path"}}""" + |> DecodeHelpers.runDecoder interop.toElm + |> .decoded + |> Expect.equal (Ok <| InteropDefinitions.FileRenamed ( "the old path", "the new path" )) + , test "fails to decode data with an unknown tag" <| + \() -> + """{"tag":"xxxxx","data":{"filePath":"a path","fileDate":"a date","fileContents":"some contents"}}""" + |> DecodeHelpers.runDecoder interop.toElm + |> .decoded + |> Result.toMaybe + |> Expect.equal Nothing + ] diff --git a/tests/Worker/SessionTests.elm b/tests/Worker/SessionTests.elm new file mode 100644 index 00000000..b0b42f30 --- /dev/null +++ b/tests/Worker/SessionTests.elm @@ -0,0 +1,204 @@ +module Worker.SessionTests exposing (suite) + +-- import BoardConfig +-- import Card +-- import Column +-- import Column.Completed as CompletedColumn +-- import Columns +-- import DragAndDrop.DragData as DragData +-- import DragAndDrop.DragTracker as DragTracker +-- import Filter +-- import GlobalSettings exposing (GlobalSettings) +-- import Helpers.FilterHelpers as FilterHelpers +-- import Helpers.TaskItemHelpers as TaskItemHelpers +-- import Parser +-- import SafeZipper + +import DataviewTaskCompletion +import Expect +import Helpers.TaskListHelpers as TaskListHelpers +import TaskItem +import TaskList +import Test exposing (..) +import Worker.InteropDefinitions exposing (Flags) +import Worker.Session as Session + + + +-- import Time + + +suite : Test +suite = + concat + [ addTaskList + , default + , finishAdding + , fromFlags + , removeTaskItems + , replaceTaskList + ] + + +addTaskList : Test +addTaskList = + describe "addTaskList" + [ test "can add a tasklist to a new session" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [ "a1", "a2" ] + , test "can add tasklists onto a session which already contains one" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.addTaskList TaskListHelpers.taskListFromFileG + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [ "a1", "a2", "g1", "g2" ] + , test "can add tasklist even if previously finished adding" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.finishAdding + |> Session.addTaskList TaskListHelpers.taskListFromFileG + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [ "a1", "a2", "g1", "g2" ] + ] + + +default : Test +default = + describe "default" + [ test "has an empty task list" <| + \() -> + Session.default + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [] + , test "has DataviewTaskCompletion.Text completion" <| + \() -> + Session.default + |> Session.dataviewTaskCompletion + |> Expect.equal (DataviewTaskCompletion.Text "completion") + ] + + +finishAdding : Test +finishAdding = + describe "finishAdding" + [ test "results in an empty tasklist if none have been added to the default Session" <| + \() -> + Session.default + |> Session.finishAdding + |> Session.taskList + |> Expect.equal TaskList.empty + , test "if Loading, there are the loaded tasks in the list" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.finishAdding + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [ "a1", "a2" ] + , test "if Loaded, stays Loaded" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.finishAdding + |> Session.finishAdding + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [ "a1", "a2" ] + ] + + +fromFlags : Test +fromFlags = + describe "fromFlags" + [ test "copies the dataviewTaskCompletion value from the flags" <| + \() -> + { exampleFlags | dataviewTaskCompletion = DataviewTaskCompletion.Emoji } + |> Session.fromFlags + |> Session.dataviewTaskCompletion + |> Expect.equal DataviewTaskCompletion.Emoji + ] + + +removeTaskItems : Test +removeTaskItems = + describe "removeTaskItems" + [ test "does nothing if there are no taskItems in the Session" <| + \() -> + Session.default + |> Session.removeTaskItems [] + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [] + , test "removes tasks with the given ids whilst loading tasklists" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.addTaskList TaskListHelpers.taskListFromFileG + |> Session.removeTaskItems [ "3826002220:2", "3792446982:3" ] + |> Session.taskList + |> TaskList.toList + |> List.map TaskItem.id + |> Expect.equal [ "3826002220:3", "3792446982:2" ] + , test "removes tasks with the given ids if finished loading tasklists" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.addTaskList TaskListHelpers.taskListFromFileG + |> Session.finishAdding + |> Session.removeTaskItems [ "3826002220:2", "3792446982:3" ] + |> Session.taskList + |> TaskList.toList + |> List.map TaskItem.id + |> Expect.equal [ "3826002220:3", "3792446982:2" ] + ] + + +replaceTaskList : Test +replaceTaskList = + describe "replaceTaskList" + [ test "does nothing if a new session (state -> Waiting)" <| + \() -> + Session.default + |> Session.replaceTaskList TaskListHelpers.taskListFromFileA + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [] + , test "replaces all tasks with those given whilst loading tasklists" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.addTaskList TaskListHelpers.taskListFromFileG + |> Session.replaceTaskList (TaskListHelpers.taskListFromNewFile "path") + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [ "n1", "n2" ] + , test "replaces all tasks with those given whilst loading tasklists even if finished adding" <| + \() -> + Session.default + |> Session.addTaskList TaskListHelpers.taskListFromFileA + |> Session.addTaskList TaskListHelpers.taskListFromFileG + |> Session.finishAdding + |> Session.replaceTaskList (TaskListHelpers.taskListFromNewFile "path") + |> Session.taskList + |> TaskList.taskTitles + |> Expect.equal [ "n1", "n2" ] + ] + + + +-- -- HELPERS + + +exampleFlags : Flags +exampleFlags = + { dataviewTaskCompletion = DataviewTaskCompletion.NoCompletion + } diff --git a/typescript/main.ts b/typescript/main.ts index 9a4ad8bb..94efe835 100644 --- a/typescript/main.ts +++ b/typescript/main.ts @@ -1,24 +1,148 @@ -import { App, Modal, Notice, Plugin, PluginSettingTab, Setting, addIcon, normalizePath } from 'obsidian'; +import { + App, + Modal, + Notice, + Plugin, + PluginSettingTab, + Setting, + TAbstractFile, + TFile, + addIcon, + moment, + normalizePath } from 'obsidian'; import { CardBoardView, VIEW_TYPE_CARD_BOARD } from './view'; -import { CardBoardPluginSettings, CardBoardPluginSettingsPostV11 } from './types'; +import { CardBoardPluginSettings, CardBoardPluginSettingsPostV11, TaskItem } from './types'; +import { Elm, ElmApp, Flags } from '../src/Worker'; +import { FileFilter } from './fileFilter' +import { getDateFromFile, IPeriodicNoteSettings } from 'obsidian-daily-notes-interface'; export default class CardBoardPlugin extends Plugin { private commandIds: string[] = []; - settings: CardBoardPluginSettings; + private fileFilter: FileFilter; + private worker: ElmApp; + settings: CardBoardPluginSettings; async onload() { - console.log('loading CardBoard plugin'); + console.log('CardBoard: loading plugin'); await this.loadSettings(); - this.app.workspace.onLayoutReady(this.onLayoutReady.bind(this)); - } - async onLayoutReady() { this.registerView( VIEW_TYPE_CARD_BOARD, (leaf) => new CardBoardView(this, leaf) ); + this.app.workspace.onLayoutReady(this.onLayoutReady.bind(this)); + } + + async onLayoutReady() { + console.debug("Cardboard: [main] onLayoutReady"); + + const globalSettings : any = this.settings?.data.globalSettings; + + if ((!(globalSettings === undefined)) && globalSettings.hasOwnProperty('filters')) { + this.fileFilter = new FileFilter(globalSettings.filters); + } else { + this.fileFilter = new FileFilter([]); + } + + // @ts-ignore + const dataviewSettings = this.app.plugins.getPlugin("dataview")?.settings + + const workerFlags:Flags = { + dataviewTaskCompletion: { + taskCompletionTracking: dataviewSettings === undefined ? true : dataviewSettings['taskCompletionTracking'], + taskCompletionUseEmojiShorthand: dataviewSettings === undefined ? false : dataviewSettings['taskCompletionUseEmojiShorthand'], + taskCompletionText: dataviewSettings === undefined ? "completion" : dataviewSettings['taskCompletionText'] + } + }; + + console.debug("CardBoard: [main] Elm.Worker.init"); + // @ts-ignore + this.worker = Elm.Worker.init({ + flags: workerFlags + }); + + const that = this; + + this.worker.ports.interopFromElm.subscribe((fromElm) => { + switch (fromElm.tag) { + case "allTaskItems": + that.handleAllTaskItems(fromElm.data); + break; + case "allTasksLoaded": + that.handleAllTasksLoaded(); + break; + case "tasksAdded": + that.handleTasksAdded(fromElm.data); + break; + case "tasksDeleted": + that.handleTasksDeleted(fromElm.data); + break; + case "tasksDeletedAndAdded": + that.handleTasksDeletedAndAdded(fromElm.data); + break; + case "tasksUpdated": + that.handleTasksUpdated(fromElm.data); + break; + } + }); + + this.registerEvent(this.app.vault.on("create", + (file) => this.handleFileCreated(file))); + + this.registerEvent(this.app.vault.on("delete", + (file) => this.handleFileDeleted(file))); + + this.registerEvent(this.app.vault.on("modify", + (file) => this.handleFileModified(file))); + + this.registerEvent(this.app.vault.on("rename", + (file, oldPath) => this.handleFileRenamed(file, oldPath))); + + const markdownFiles = this.app.vault.getMarkdownFiles(); + const filteredFiles = markdownFiles.filter((file) => this.fileFilter.isAllowed(file.path)); + + for (const file of filteredFiles) { + const fileDate = this.formattedFileDate(file); + const fileContents = await this.app.vault.cachedRead(file); + + this.worker.ports.interopToElm.send({ + tag: "fileAdded", + data: { + filePath: file.path, + fileDate: fileDate, + fileContents: fileContents + } + }); + } + + console.debug("CardBoard: [main] toWorker <- allMarkdownLoaded"); + this.worker.ports.interopToElm.send({ + tag: "allMarkdownLoaded", + data: { } + }); + + console.debug("CardBoard: " + markdownFiles.length + " markdown files in vault."); + console.log("CardBoard: " + filteredFiles.length + " files scanned for tasks."); + } + + + async handleAllTaskItems(taskItems: TaskItem[]) { + console.debug("CardBoard: [main] fromWorker -> allTasksItems"); + + const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_CARD_BOARD); + + for (const leaf of leaves) { + if (leaf.view instanceof CardBoardView) { + leaf.view.taskItemsRefreshed(taskItems); + } + } + } + + async handleAllTasksLoaded() { + console.debug("CardBoard: [main] fromWorker -> allTasksLoaded"); + addIcon("card-board", '' + '' + @@ -31,6 +155,60 @@ export default class CardBoardPlugin extends Plugin { this.addCommands(); } + async viewInitialized() { + console.debug("CardBoard: [main] toWorker <- viewInitialized"); + this.worker?.ports.interopToElm.send({ + tag: "viewInitialized" + }); + } + + + async handleTasksAdded(taskItems : TaskItem[]) { + const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_CARD_BOARD); + + for (const leaf of leaves) { + if (leaf.view instanceof CardBoardView) { + leaf.view.taskItemsAdded(taskItems); + } + } + } + + async handleTasksDeleted(taskIds : string[]) { + console.debug("CardBoard: [main] fromWorker -> tasksDeleted"); + + const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_CARD_BOARD); + + for (const leaf of leaves) { + if (leaf.view instanceof CardBoardView) { + leaf.view.taskItemsDeleted(taskIds); + } + } + } + + async handleTasksDeletedAndAdded(toDeleteAndAdd : [string[], TaskItem[]]) { + console.debug("CardBoard: [main] fromWorker -> tasksDeletedAndAdded: (" + toDeleteAndAdd[0].length + ", " + toDeleteAndAdd[1].length + ")"); + + const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_CARD_BOARD); + + for (const leaf of leaves) { + if (leaf.view instanceof CardBoardView) { + leaf.view.taskItemsDeletedAndAdded(toDeleteAndAdd); + } + } + } + + async handleTasksUpdated(updateDetails : [string, TaskItem][]) { + console.debug("CardBoard: [main] fromWorker -> tasksUpdated: " + updateDetails.length); + + const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_CARD_BOARD); + + for (const leaf of leaves) { + if (leaf.view instanceof CardBoardView) { + leaf.view.taskItemsUpdated(updateDetails); + } + } + } + onunload() { console.log('unloading CardBoard plugin'); this.app.workspace.detachLeavesOfType(VIEW_TYPE_CARD_BOARD); @@ -114,4 +292,94 @@ export default class CardBoardPlugin extends Plugin { this.app.vault.adapter.copy(pathToSettings, pathToSavedSettings); } } + + async handleFileCreated( + file: TAbstractFile + ) { + if (file instanceof TFile) { + if (this.fileFilter.isAllowed(file.path)) { + const fileDate = this.formattedFileDate(file); + const fileContents = await this.app.vault.read(file); + + this.worker.ports.interopToElm.send({ + tag: "fileAdded", + data: { + filePath: file.path, + fileDate: fileDate, + fileContents: fileContents + } + }); + } + } + } + + async handleFileDeleted( + file: TAbstractFile + ) { + if (file instanceof TFile) { + this.worker.ports.interopToElm.send({ + tag: "fileDeleted", + data: file.path + }); + } + } + + async handleFileModified( + file: TAbstractFile + ) { + if (file instanceof TFile) { + if (this.fileFilter.isAllowed(file.path)) { + const fileDate = this.formattedFileDate(file); + const fileContents = await this.app.vault.read(file); + + this.worker.ports.interopToElm.send({ + tag: "fileModified", + data: { + filePath: file.path, + fileDate: fileDate, + fileContents: fileContents + } + }); + } + } + } + + async handleFileRenamed( + file: TAbstractFile, + oldPath: string + ) { + let oldNew : [boolean, boolean] = [this.fileFilter.isAllowed(oldPath), this.fileFilter.isAllowed(file.path)]; + + switch(oldNew.join(",")) { + case 'false,true': { + this.handleFileCreated(file) + break; + } + case 'true,false': { + this.worker.ports.interopToElm.send({ + tag: "fileDeleted", + data: oldPath + }); + break; + } + case 'true,true': { + this.worker.ports.interopToElm.send({ + tag: "fileRenamed", + data: { + oldPath: oldPath, + newPath: file.path + } + }); + break; + } + } + } + + // HELPERS + + formattedFileDate( + file: TFile + ): string | null { + return getDateFromFile(file, "day")?.format('YYYY-MM-DD') || null; + } } diff --git a/typescript/types.ts b/typescript/types.ts index 845acbc3..c0b210c1 100644 --- a/typescript/types.ts +++ b/typescript/types.ts @@ -1,5 +1,59 @@ export type CardBoardPluginSettings = CardBoardPluginSettingsPreV11 | CardBoardPluginSettingsPostV11; +type AutoCompleteTag = + { tag : "FalseSpecified" | "NotSpecifed" | "TrueSpecified" } + +type AutoComplete = + { tag : "AutoCompleteTag", data : AutoCompleteTag } + +type CompletionTag = + { tag : "Completed" } | + { tag : "CompletedAt", data : number } | + { tag : "Incomplete" } + +type Completion = + { tag : "CompletedTag", data : number } + +type DueTag = + { tag : "NotSet" } | + { tag : "SetToDate", date : number } | + { tag : "SetToNone" } + +type Due = { + tag : "DueTag", + data : DueTag +} + +type Tag = { + tag : "ObsidianTag", + data : string +} + +type Word = + { tag : "Word", data : string } + +type Content = AutoComplete | Completion | Due | Tag | Word; + +export type TaskItemFields = { + autoComplete : AutoCompleteTag; + completion : CompletionTag; + contents : Content[]; + dueFile : number | null; + dueTag : DueTag; + filePath : string; + lineNumber : number; + notes : string; + originalBlock : string; + originalLine : string; + tags : string[]; + title : string[]; +} + +export type TaskItem = { + fields : TaskItemFields; + subFields : TaskItemFields[]; +} + export type CardBoardPluginSettingsPostV11 = { data : { boardConfigs : ({ diff --git a/typescript/view.ts b/typescript/view.ts index 0757e286..87a400f0 100644 --- a/typescript/view.ts +++ b/typescript/view.ts @@ -16,7 +16,7 @@ import { import { Elm, ElmApp, Flags } from '../src/Main'; import CardBoardPlugin from './main'; -import { CardBoardPluginSettingsPostV11 } from './types'; +import { CardBoardPluginSettingsPostV11, TaskItem } from './types'; import { getDateFromFile, IPeriodicNoteSettings } from 'obsidian-daily-notes-interface'; import { FileFilter } from './fileFilter' import { Scrollable } from './scrollable' @@ -107,7 +107,7 @@ export class CardBoardView extends ItemView { that.handleDisplayTaskMarkdown(fromElm.data); break; case "elmInitialized": - that.handleElmInitialized(); + that.handleElmInitialized(fromElm.data); break; case "openTaskSourceFile": that.handleOpenTaskSourceFile(fromElm.data); @@ -133,18 +133,6 @@ export class CardBoardView extends ItemView { this.registerEvent(this.app.workspace.on("active-leaf-change", (leaf) => this.handleActiveLeafChange(leaf))); - this.registerEvent(this.app.vault.on("create", - (file) => this.handleFileCreated(file))); - - this.registerEvent(this.app.vault.on("delete", - (file) => this.handleFileDeleted(file))); - - this.registerEvent(this.app.vault.on("modify", - (file) => this.handleFileModified(file))); - - this.registerEvent(this.app.vault.on("rename", - (file, oldPath) => this.handleFileRenamed(file, oldPath))); - // @ts-ignore this.registerEvent(this.app.vault.on("config-changed", () => this.handleConfigChanged())); @@ -202,7 +190,7 @@ export class CardBoardView extends ItemView { data: { filePath: string, lineNumber: number, - originalText: string} + originalLine: string} ) { const file = this.app.vault.getAbstractFileByPath(data.filePath) @@ -210,7 +198,7 @@ export class CardBoardView extends ItemView { const markdown = await this.vault.read(file) const markdownLines = markdown.split(/\r?\n/) - if (markdownLines[data.lineNumber - 1].includes(data.originalText)) { + if (markdownLines[data.lineNumber - 1].includes(data.originalLine)) { markdownLines[data.lineNumber - 1] = markdownLines[data.lineNumber - 1].replace(/^(.*)$/, "$1") this.vault.modify(file, markdownLines.join("\n")) } @@ -279,28 +267,10 @@ export class CardBoardView extends ItemView { }) } - async handleElmInitialized() { - const markdownFiles = this.vault.getMarkdownFiles(); - const filteredFiles = markdownFiles.filter((file) => this.fileFilter.isAllowed(file.path)); - - for (const file of filteredFiles) { - const fileDate = this.formattedFileDate(file); - const fileContents = await this.vault.cachedRead(file); - - this.elm.ports.interopToElm.send({ - tag: "fileAdded", - data: { - filePath: file.path, - fileDate: fileDate, - fileContents: fileContents - } - }); - } + async handleElmInitialized(uniqueId : string) { + console.debug("CardBoard: [view] fromView -> elmInitialised"); - this.elm.ports.interopToElm.send({ - tag: "allMarkdownLoaded", - data: { } - }); + this.plugin.viewInitialized(); } @@ -308,7 +278,7 @@ export class CardBoardView extends ItemView { data: { filePath: string, lineNumber: number, - originalText: string + originalLine: string } ) { await this.openOrSwitchWithHighlight(this.app, data.filePath, data.lineNumber); @@ -528,10 +498,53 @@ export class CardBoardView extends ItemView { }); } + taskItemsRefreshed(taskItems: TaskItem[]) { + console.debug("CardBoard: [view] toView <- taskItemsRefreshed: " + taskItems.length); + + this.elm.ports.interopToElm.send({ + tag: "taskItemsRefreshed", + data: taskItems + }); + } + + taskItemsAdded(taskItems: TaskItem[]) { + this.elm.ports.interopToElm.send({ + tag: "taskItemsAdded", + data: taskItems + }); + } + + taskItemsDeleted(taskIds: string[]) { + console.debug("CardBoard: [view] toView <- taskItemsDeleted: " + taskIds.length); + + this.elm.ports.interopToElm.send({ + tag: "taskItemsDeleted", + data: taskIds + }); + } + + taskItemsDeletedAndAdded(toDeleteAndAdd : [string[], TaskItem[]]) { + console.debug("CardBoard: [view] toView <- taskItemsDeletedAndAdded: (" + toDeleteAndAdd[0].length + ", " + toDeleteAndAdd[1].length + ")"); + + this.elm.ports.interopToElm.send({ + tag: "taskItemsDeletedAndAdded", + data: toDeleteAndAdd + }); + } + + taskItemsUpdated(updateDetails : [string, TaskItem][]) { + console.debug("CardBoard: [view] toView <- taskItemsUpdated: " + updateDetails.length); + + this.elm.ports.interopToElm.send({ + tag: "taskItemsUpdated", + data: updateDetails + }); + } + async handleUpdateTasks( data: { filePath: string, - tasks: { lineNumber: number, originalText: string, newText: string }[] + tasks: { lineNumber: number, originalLine: string, newText: string }[] }) { const file = this.app.vault.getAbstractFileByPath(data.filePath) @@ -540,7 +553,7 @@ export class CardBoardView extends ItemView { const markdownLines = markdown.split(/\r?\n/) for (const item of data.tasks) { - if (markdownLines[item.lineNumber - 1].includes(item.originalText)) { + if (markdownLines[item.lineNumber - 1].includes(item.originalLine)) { markdownLines[item.lineNumber - 1] = item.newText } } @@ -573,88 +586,6 @@ export class CardBoardView extends ItemView { }); } - async handleFileCreated( - file: TAbstractFile - ) { - if (file instanceof TFile) { - if (this.fileFilter.isAllowed(file.path)) { - const fileDate = this.formattedFileDate(file); - const fileContents = await this.vault.read(file); - - this.elm.ports.interopToElm.send({ - tag: "fileAdded", - data: { - filePath: file.path, - fileDate: fileDate, - fileContents: fileContents - } - }); - } - } - } - - async handleFileDeleted( - file: TAbstractFile - ) { - if (file instanceof TFile) { - this.elm.ports.interopToElm.send({ - tag: "fileDeleted", - data: file.path - }); - } - } - - async handleFileModified( - file: TAbstractFile - ) { - if (file instanceof TFile) { - if (this.fileFilter.isAllowed(file.path)) { - const fileDate = this.formattedFileDate(file); - const fileContents = await this.vault.read(file); - - this.elm.ports.interopToElm.send({ - tag: "fileUpdated", - data: { - filePath: file.path, - fileDate: fileDate, - fileContents: fileContents - } - }); - } - } - } - - async handleFileRenamed( - file: TAbstractFile, - oldPath: string - ) { - let oldNew : [boolean, boolean] = [this.fileFilter.isAllowed(oldPath), this.fileFilter.isAllowed(file.path)]; - - switch(oldNew.join(",")) { - case 'false,true': { - this.handleFileCreated(file) - break; - } - case 'true,false': { - this.elm.ports.interopToElm.send({ - tag: "fileDeleted", - data: oldPath - }); - break; - } - case 'true,true': { - this.elm.ports.interopToElm.send({ - tag: "fileRenamed", - data: { - oldPath: oldPath, - newPath: file.path - } - }); - break; - } - } - } - // HELPERS formattedFileDate( diff --git a/webpack.config.js b/webpack.common.js similarity index 60% rename from webpack.config.js rename to webpack.common.js index f8d3e81d..63e47587 100644 --- a/webpack.config.js +++ b/webpack.common.js @@ -2,6 +2,9 @@ const path = require('path'); module.exports = { entry: './typescript/main.ts', + performance: { + hints: false + }, output: { path: path.resolve(__dirname, '.'), libraryTarget: 'commonjs', @@ -13,17 +16,6 @@ module.exports = { test: /\.ts$/, use: 'ts-loader', }, - { - test: [/\.elm$/], - exclude: [/elm-stuff/, /node_modules/], - use: [ - { loader: "elm-reloader" }, - { - loader: "elm-webpack-loader", - options: {} - } - ] - }, ] }, externals: { diff --git a/webpack.dev.js b/webpack.dev.js new file mode 100644 index 00000000..2f5cb827 --- /dev/null +++ b/webpack.dev.js @@ -0,0 +1,23 @@ +const path = require('path'); + +const { merge } = require("webpack-merge"); +const common = require("./webpack.common.js"); + +module.exports = merge(common, { + mode: "development", + module: { + rules: [ + { + test: [/\.elm$/], + exclude: [/elm-stuff/, /node_modules/], + use: [ + { loader: "elm-reloader" }, + { + loader: "elm-webpack-loader", + options: {} + } + ] + }, + ] + }, +}); diff --git a/webpack.prod.js b/webpack.prod.js new file mode 100644 index 00000000..f4a8a8a6 --- /dev/null +++ b/webpack.prod.js @@ -0,0 +1,53 @@ +const path = require("path"); +const { merge } = require("webpack-merge"); +const common = require("./webpack.common.js"); +const TerserPlugin = require("terser-webpack-plugin"); + +module.exports = merge(common, { + mode: "production", + optimization: { + 'minimize': true, + minimizer: [new TerserPlugin({ + terserOptions: { + compress: { + pure_funcs: [ + 'console.info', + 'console.debug', + 'console.warn', + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9" + + ] + } + } + })], + }, + module: { + rules: [ + { + test: [/\.elm$/], + exclude: [/elm-stuff/, /node_modules/], + use: [ + { + loader: "elm-webpack-loader", + options: {} + } + ] + }, + ] + }, +});