From 0e397b34fd5b44fc841f6a87263107070f327032 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Tue, 25 Apr 2023 12:44:48 -0500 Subject: [PATCH 1/3] fix: broken frontend development (#2357) * dependency hell be like * moved resolution to subdependency only --- frontend/package.json | 2 + frontend/yarn.lock | 882 +++++++++++------------------------------- 2 files changed, 224 insertions(+), 660 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 7de41474e4a..3746039f03e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -54,8 +54,10 @@ "vitest": "^0.29.0" }, "resolutions": { + "@nuxtjs/i18n/**/ufo": "0.7.9", "vue-template-compiler": "2.7.14", "vue-demi": "^0.13.11", + "postcss-preset-env": "^7.0.0", "typescript": "^4.9.5" } } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 07c6ead630a..e25b5a32ee9 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -994,186 +994,108 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@csstools/cascade-layer-name-parser@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.1.tgz#5957adeb71be8159e543d37a9c48e124dcd6c32e" - integrity sha512-SAAi5DpgJJWkfTvWSaqkgyIsTawa83hMwKrktkj6ra2h+q6ZN57vOGZ6ySHq6RSo+CbP64fA3aPChPBRDDUgtw== - -"@csstools/color-helpers@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-1.0.0.tgz#7097722a51da1e9e622345ca000261f1ae6e8f58" - integrity sha512-tgqtiV8sU/VaWYjOB3O7PWs7HR/MmOLl2kTYRW2qSsTSEniJq7xmyAYFB1LPpXvvQcE5u2ih2dK9fyc8BnrAGQ== - -"@csstools/convert-colors@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" - integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== - -"@csstools/css-calc@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-1.0.0.tgz#f93e4cc9d80b504467aee1b5251bb2fd8be435a7" - integrity sha512-Xw0b/Jr+vLGGYD8cxsGWPaY5n1GtVC6G4tcga+eZPXZzRjjZHorPwW739UgtXzL2Da1RLxNE73c0r/KvmizPsw== - -"@csstools/css-parser-algorithms@^2.0.0", "@csstools/css-parser-algorithms@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.0.1.tgz#ff02629c7c95d1f4f8ea84d5ef1173461610535e" - integrity sha512-B9/8PmOtU6nBiibJg0glnNktQDZ3rZnGn/7UmDfrm2vMtrdlXO3p7ErE95N0up80IRk9YEtB5jyj/TmQ1WH3dw== - -"@csstools/css-tokenizer@^2.0.0", "@csstools/css-tokenizer@^2.0.1": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.1.0.tgz#fee4de3d444db3ce9007f3af6474af8ba3e4b930" - integrity sha512-dtqFyoJBHUxGi9zPZdpCKP1xk8tq6KPHJ/NY4qWXiYo6IcSGwzk3L8x2XzZbbyOyBs9xQARoGveU2AsgLj6D2A== - -"@csstools/media-query-list-parser@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.1.tgz#d85a366811563a5d002755ed10e5212a1613c91d" - integrity sha512-X2/OuzEbjaxhzm97UJ+95GrMeT29d1Ib+Pu+paGLuRWZnWRK9sI9r3ikmKXPWGA1C4y4JEdBEFpp9jEqCvLeRA== - -"@csstools/postcss-cascade-layers@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-3.0.1.tgz#d839386e90428b448e3f75276bc01d516e852a0d" - integrity sha512-dD8W98dOYNOH/yX4V4HXOhfCOnvVAg8TtsL+qCGNoKXuq5z2C/d026wGWgySgC8cajXXo/wNezS31Glj5GcqrA== +"@csstools/postcss-cascade-layers@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz#8a997edf97d34071dd2e37ea6022447dd9e795ad" + integrity sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA== dependencies: "@csstools/selector-specificity" "^2.0.2" postcss-selector-parser "^6.0.10" -"@csstools/postcss-color-function@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-2.1.0.tgz#2ed0320b4f7e0d7718280e273f88ba4164eae7e7" - integrity sha512-XBoCClLyWchlYGHGlmMOa6M2UXZNrZm63HVfsvgD/z1RPm/s3+FhHyT6VkDo+OvEBPhCgn6xz4IeCu4pRctKDQ== +"@csstools/postcss-color-function@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz#2bd36ab34f82d0497cfacdc9b18d34b5e6f64b6b" + integrity sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw== dependencies: - "@csstools/color-helpers" "^1.0.0" - "@csstools/postcss-progressive-custom-properties" "^2.0.0" + "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-font-format-keywords@^2.0.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-2.0.2.tgz#d798d96f4af6cddcfee459f598c976e6011042d2" - integrity sha512-iKYZlIs6JsNT7NKyRjyIyezTCHLh4L4BBB3F5Nx7Dc4Z/QmBgX+YJFuUSar8IM6KclGiAUFGomXFdYxAwJydlA== +"@csstools/postcss-font-format-keywords@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz#677b34e9e88ae997a67283311657973150e8b16a" + integrity sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-hwb-function@^2.0.0": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-2.1.1.tgz#a86efbae9f95236022d6bed83e06357a3bfb4de7" - integrity sha512-XijKzdxBdH2hU6IcPWmnaU85FKEF1XE5hGy0d6dQC6XznFUIRu1T4uebL3krayX40m4xIcxfCBsQm5zphzVrtg== +"@csstools/postcss-hwb-function@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz#ab54a9fce0ac102c754854769962f2422ae8aa8b" + integrity sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w== dependencies: - "@csstools/color-helpers" "^1.0.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-ic-unit@^2.0.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-2.0.2.tgz#5a5e481c53977deec3d63793788eec924d4c5f7d" - integrity sha512-N84qGTJkfLTPj2qOG5P4CIqGjpZBbjOEMKMn+UjO5wlb9lcBTfBsxCF0lQsFdWJUzBHYFOz19dL66v71WF3Pig== +"@csstools/postcss-ic-unit@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz#28237d812a124d1a16a5acc5c3832b040b303e58" + integrity sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw== dependencies: - "@csstools/postcss-progressive-custom-properties" "^2.0.0" + "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-is-pseudo-class@^3.0.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-3.1.1.tgz#81b0f3ba388bf3c8966e1a4413e1839beef7960e" - integrity sha512-hhiacuby4YdUnnxfCYCRMBIobyJImozf0u+gHSbQ/tNOdwvmrZtVROvgW7zmfYuRkHVDNZJWZslq2v5jOU+j/A== +"@csstools/postcss-is-pseudo-class@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz#846ae6c0d5a1eaa878fce352c544f9c295509cd1" + integrity sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA== dependencies: "@csstools/selector-specificity" "^2.0.0" postcss-selector-parser "^6.0.10" -"@csstools/postcss-logical-float-and-clear@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-1.0.1.tgz#d255ea7aad18880930b63d8a04164f56182f2ecf" - integrity sha512-eO9z2sMLddvlfFEW5Fxbjyd03zaO7cJafDurK4rCqyRt9P7aaWwha0LcSzoROlcZrw1NBV2JAp2vMKfPMQO1xw== - -"@csstools/postcss-logical-resize@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-resize/-/postcss-logical-resize-1.0.1.tgz#826d3de929d7d786c32c2c118f78e813a1c2cdec" - integrity sha512-x1ge74eCSvpBkDDWppl+7FuD2dL68WP+wwP2qvdUcKY17vJksz+XoE1ZRV38uJgS6FNUwC0AxrPW5gy3MxsDHQ== +"@csstools/postcss-nested-calc@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz#d7e9d1d0d3d15cf5ac891b16028af2a1044d0c26" + integrity sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-logical-viewport-units@^1.0.0": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-1.0.2.tgz#b968c57e1459429b48b24e2c250ca0904d71df6e" - integrity sha512-nnKFywBqRMYjv5jyjSplD/nbAnboUEGFfdxKw1o34Y1nvycgqjQavhKkmxbORxroBBIDwC5y6SfgENcPPUcOxQ== - dependencies: - "@csstools/css-tokenizer" "^2.0.0" - -"@csstools/postcss-media-queries-aspect-ratio-number-values@^1.0.0": +"@csstools/postcss-normalize-display-values@^1.0.1": version "1.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-1.0.1.tgz#fd6a8e50c2d6d46a5c95b6cdc5563a091fa2f0fa" - integrity sha512-V9yQqXdje6OfqDf6EL5iGOpi6N0OEczwYK83rql9UapQwFEryXlAehR5AqH8QqLYb6+y31wUXK6vMxCp0920Zg== - dependencies: - "@csstools/css-parser-algorithms" "^2.0.0" - "@csstools/css-tokenizer" "^2.0.0" - "@csstools/media-query-list-parser" "^2.0.0" - -"@csstools/postcss-nested-calc@^2.0.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@csstools/postcss-nested-calc/-/postcss-nested-calc-2.0.2.tgz#a0857650ef88b1aa7b094c7ea8ea1378c35695e0" - integrity sha512-jbwrP8rN4e7LNaRcpx3xpMUjhtt34I9OV+zgbcsYAAk6k1+3kODXJBf95/JMYWhu9g1oif7r06QVUgfWsKxCFw== + resolved "https://registry.yarnpkg.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz#15da54a36e867b3ac5163ee12c1d7f82d4d612c3" + integrity sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-normalize-display-values@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-2.0.1.tgz#35dc188c5b4713cf902959fe3c8ce613fcb7543e" - integrity sha512-TQT5g3JQ5gPXC239YuRK8jFceXF9d25ZvBkyjzBGGoW5st5sPXFVQS8OjYb9IJ/K3CdfK4528y483cgS2DJR/w== +"@csstools/postcss-oklab-function@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz#88cee0fbc8d6df27079ebd2fa016ee261eecf844" + integrity sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA== dependencies: + "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-oklab-function@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-2.1.0.tgz#0f9b914574549e617942a40297ffe76da76809c6" - integrity sha512-U/odSNjOVhagNRu+RDaNVbn8vaqA9GyCOoneQA2je7697KOrtRDc7/POrYsP7QioO2aaezDzKNX02wBzc99fkQ== +"@csstools/postcss-progressive-custom-properties@^1.1.0", "@csstools/postcss-progressive-custom-properties@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz#542292558384361776b45c85226b9a3a34f276fa" + integrity sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA== dependencies: - "@csstools/color-helpers" "^1.0.0" - "@csstools/postcss-progressive-custom-properties" "^2.0.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-progressive-custom-properties@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-2.1.0.tgz#df35da0e85a6d5e1d8e4c9a0ba9032e8486e8b9d" - integrity sha512-tRX1rinsXajZlc4WiU7s9Y6O9EdSHScT997zDsvDUjQ1oZL2nvnL6Bt0s9KyQZZTdC3lrG2PIdBqdOIWXSEPlQ== +"@csstools/postcss-stepped-value-functions@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz#f8772c3681cc2befed695e2b0b1d68e22f08c4f4" + integrity sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ== dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-scope-pseudo-class@^2.0.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-2.0.2.tgz#6325e1e3b321093c59b008ec670bb772e17f06fe" - integrity sha512-6Pvo4uexUCXt+Hz5iUtemQAcIuCYnL+ePs1khFR6/xPgC92aQLJ0zGHonWoewiBE+I++4gXK3pr+R1rlOFHe5w== - dependencies: - postcss-selector-parser "^6.0.10" - -"@csstools/postcss-stepped-value-functions@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-2.1.0.tgz#8ca134a7f70e00b14f5796fb32ed33a656ba0b1b" - integrity sha512-CkEo9BF8fQeMoXW3biXjlgTLY7PA4UFihn6leq7hPoRzIguLUI0WZIVgsITGXfX8LXmkhCSTjXO2DLYu/LUixQ== - dependencies: - "@csstools/css-calc" "^1.0.0" - "@csstools/css-parser-algorithms" "^2.0.1" - "@csstools/css-tokenizer" "^2.0.1" - -"@csstools/postcss-text-decoration-shorthand@^2.0.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-2.2.1.tgz#1accf54487d74aa7ccade2e674c7290bb2ce10ae" - integrity sha512-Ow6/cWWdjjVvA83mkm3kLRvvWsbzoe1AbJCxkpC+c9ibUjyS8pifm+LpZslQUKcxRVQ69ztKHDBEbFGTDhNeUw== +"@csstools/postcss-text-decoration-shorthand@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz#ea96cfbc87d921eca914d3ad29340d9bcc4c953f" + integrity sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw== dependencies: - "@csstools/color-helpers" "^1.0.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-trigonometric-functions@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-2.1.0.tgz#5a489975d445b9e79c6cb5f0f01ae711ec86639d" - integrity sha512-Ly7YczO+QdnByYeGqlppJoA2Tb2vsFfj5gSrszPTXJ+/4g3nnEZnG0VSeTK/WA8y7fzyL/qVNkkdEeOnruNWFQ== +"@csstools/postcss-trigonometric-functions@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz#94d3e4774c36d35dcdc88ce091336cb770d32756" + integrity sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og== dependencies: - "@csstools/css-calc" "^1.0.0" - "@csstools/css-parser-algorithms" "^2.0.1" - "@csstools/css-tokenizer" "^2.0.1" + postcss-value-parser "^4.2.0" -"@csstools/postcss-unset-value@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@csstools/postcss-unset-value/-/postcss-unset-value-2.0.1.tgz#67091dd6cff556bff896c95053eb070cc6b21c25" - integrity sha512-oJ9Xl29/yU8U7/pnMJRqAZd4YXNCfGEdcP4ywREuqm/xMqcgDNDppYRoCGDt40aaZQIEKBS79LytUDN/DHf0Ew== +"@csstools/postcss-unset-value@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz#c99bb70e2cdc7312948d1eb41df2412330b81f77" + integrity sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g== -"@csstools/selector-specificity@^2.0.0", "@csstools/selector-specificity@^2.0.1", "@csstools/selector-specificity@^2.0.2": +"@csstools/selector-specificity@^2.0.0", "@csstools/selector-specificity@^2.0.2": version "2.2.0" resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz#2cbcf822bf3764c9658c4d2e568bd0c0cb748016" integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw== @@ -3205,19 +3127,6 @@ autoprefixer@^10.4.13: picocolors "^1.0.0" postcss-value-parser "^4.2.0" -autoprefixer@^9.6.1: - version "9.8.8" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a" - integrity sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA== - dependencies: - browserslist "^4.12.0" - caniuse-lite "^1.0.30001109" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - picocolors "^0.2.1" - postcss "^7.0.32" - postcss-value-parser "^4.1.0" - available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" @@ -3480,7 +3389,7 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4.21.5, browserslist@^4.6.4: +browserslist@^4.0.0, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4.21.5: version "4.21.5" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== @@ -3704,7 +3613,7 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001467: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001467: version "1.0.30001469" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001469.tgz#3dd505430c8522fdc9f94b4a19518e330f5c945a" integrity sha512-Rcp7221ScNqQPP3W+lVOYDyjdR6dC+neEQCttoNr5bAyz54AboB4iwpnWgyi8P4YUsPybVzT4LgWiBbI3drL4g== @@ -4186,41 +4095,24 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -css-blank-pseudo@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" - integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w== - dependencies: - postcss "^7.0.5" - -css-blank-pseudo@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-5.0.2.tgz#3df5cd950f64de960974da05e76954fd3d7442f9" - integrity sha512-aCU4AZ7uEcVSUzagTlA9pHciz7aWPKA/YzrEkpdSopJ2pvhIxiQ5sYeMz1/KByxlIo4XBdvMNJAVKMg/GRnhfw== +css-blank-pseudo@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561" + integrity sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ== dependencies: - postcss-selector-parser "^6.0.10" + postcss-selector-parser "^6.0.9" css-declaration-sorter@^6.3.1: version "6.4.0" resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz#630618adc21724484b3e9505bce812def44000ad" integrity sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew== -css-has-pseudo@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" - integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ== - dependencies: - postcss "^7.0.6" - postcss-selector-parser "^5.0.0-rc.4" - -css-has-pseudo@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-5.0.2.tgz#8798118c705d858b7aeb9d839a39edd901c1cc83" - integrity sha512-q+U+4QdwwB7T9VEW/LyO6CFrLAeLqOykC5mDqJXc7aKZAhDbq7BvGT13VGJe+IwBfdN2o3Xdw2kJ5IxwV1Sc9Q== +css-has-pseudo@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz#57f6be91ca242d5c9020ee3e51bbb5b89fc7af73" + integrity sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw== dependencies: - "@csstools/selector-specificity" "^2.0.1" - postcss-selector-parser "^6.0.10" - postcss-value-parser "^4.2.0" + postcss-selector-parser "^6.0.9" css-loader@^5.2.7: version "5.2.7" @@ -4238,17 +4130,10 @@ css-loader@^5.2.7: schema-utils "^3.0.0" semver "^7.3.5" -css-prefers-color-scheme@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" - integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg== - dependencies: - postcss "^7.0.5" - -css-prefers-color-scheme@^8.0.0: - version "8.0.2" - resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-8.0.2.tgz#a0671f54eb19ed0d30b952574c0af11ec355fb6d" - integrity sha512-OvFghizHJ45x7nsJJUSYLyQNTzsCU8yWjxAc/nhPQg1pbs18LMoET8N3kOweFDPy0JV0OSXN2iqRFhPBHYOeMA== +css-prefers-color-scheme@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz#ca8a22e5992c10a5b9d315155e7caee625903349" + integrity sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA== css-select@^4.1.3: version "4.3.0" @@ -4285,20 +4170,10 @@ css-what@^6.0.1, css-what@^6.1.0: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== -cssdb@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" - integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ== - -cssdb@^7.4.0: - version "7.4.1" - resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-7.4.1.tgz#61d55c0173126689922a219e15e131e4b5caf422" - integrity sha512-0Q8NOMpXJ3iTDDbUv9grcmQAfdDx4qz+fN/+Md2FGbevT+6+bJNQ2LjB2YIUlLbpBTM32idU1Sb+tb/uGt6/XQ== - -cssesc@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" - integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== +cssdb@^7.1.0: + version "7.5.4" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-7.5.4.tgz#e34dafee5184d67634604e345e389ca79ac179ea" + integrity sha512-fGD+J6Jlq+aurfE1VDXlLS4Pt0VtNlu2+YgfGOdMxRyl/HQ9bDiHTwSck1Yz8A97Dt/82izSK6Bp/4nVqacOsg== cssesc@^3.0.0: version "3.0.0" @@ -5702,11 +5577,6 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -flatten@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" - integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== - flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" @@ -6485,11 +6355,6 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA== - infer-owner@^1.0.3, infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" @@ -6932,7 +6797,7 @@ jest-worker@^29.4.1: supports-color "^8.0.0" jimp-compact@^0.16.1: - version "0.16.2" + version "0.16.1" resolved "https://registry.yarnpkg.com/jimp-compact/-/jimp-compact-0.16.2.tgz#650bf90698eb7af797e20bf5f5cdccd137e7d08a" integrity sha512-F/r0L283J46xiGUEvUb3jxUsei7aB94g3NRIMuJ4WhbpEcJV2U5GpaUNJLnBiOP2+x4lLTI4UiRVrmbrXQAOMA== @@ -8044,11 +7909,6 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - integrity sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg== - nuxt-vite@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/nuxt-vite/-/nuxt-vite-0.2.3.tgz#cd75334ec1250141572b134386777421d724de55" @@ -8584,18 +8444,10 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== -postcss-attribute-case-insensitive@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" - integrity sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^6.0.2" - -postcss-attribute-case-insensitive@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.2.tgz#e843091859323342e461878d201ee70278809e01" - integrity sha512-IRuCwwAAQbgaLhxQdQcIIK0dCVXg3XDUnzgKD8iwdiYdwU4rMWRWyl/W9/0nA4ihVpq5pyALiHB2veBJ0292pw== +postcss-attribute-case-insensitive@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz#03d761b24afc04c09e757e92ff53716ae8ea2741" + integrity sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ== dependencies: postcss-selector-parser "^6.0.10" @@ -8614,66 +8466,24 @@ postcss-clamp@^4.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-color-functional-notation@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" - integrity sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-color-functional-notation@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-5.0.2.tgz#6d03c928aa3a13487703af86c301bdcd501e7430" - integrity sha512-M6ygxWOyd6eWf3sd1Lv8xi4SeF4iBPfJvkfMU4ITh8ExJc1qhbvh/U8Cv/uOvBgUVOMDdScvCdlg8+hREQzs7w== +postcss-color-functional-notation@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz#21a909e8d7454d3612d1659e471ce4696f28caec" + integrity sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg== dependencies: postcss-value-parser "^4.2.0" -postcss-color-gray@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" - integrity sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw== - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.5" - postcss-values-parser "^2.0.0" - -postcss-color-hex-alpha@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" - integrity sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw== - dependencies: - postcss "^7.0.14" - postcss-values-parser "^2.0.1" - -postcss-color-hex-alpha@^9.0.0: - version "9.0.2" - resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.2.tgz#6d3ed50342802469880981a1999515d003ff7d79" - integrity sha512-SfPjgr//VQ/DOCf80STIAsdAs7sbIbxATvVmd+Ec7JvR8onz9pjawhq3BJM3Pie40EE3TyB0P6hft16D33Nlyg== +postcss-color-hex-alpha@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz#c66e2980f2fbc1a63f5b079663340ce8b55f25a5" + integrity sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ== dependencies: postcss-value-parser "^4.2.0" -postcss-color-mod-function@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" - integrity sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ== - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-color-rebeccapurple@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" - integrity sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-color-rebeccapurple@^8.0.0: - version "8.0.2" - resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-8.0.2.tgz#c0f2dcf1ef4dd393314920aa181cca8c390a2648" - integrity sha512-xWf/JmAxVoB5bltHpXk+uGRoGFwu4WDAR7210el+iyvTdqiKpDhtcT8N3edXMoVJY0WHFMrKMUieql/wRNiXkw== +postcss-color-rebeccapurple@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz#63fdab91d878ebc4dd4b7c02619a0c3d6a56ced0" + integrity sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg== dependencies: postcss-value-parser "^4.2.0" @@ -8695,71 +8505,31 @@ postcss-convert-values@^5.1.3: browserslist "^4.21.4" postcss-value-parser "^4.2.0" -postcss-custom-media@^7.0.8: - version "7.0.8" - resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" - integrity sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg== - dependencies: - postcss "^7.0.14" - -postcss-custom-media@^9.1.0: - version "9.1.2" - resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-9.1.2.tgz#0e9c34b0b5325edc2c5f42f47543cb64d777764b" - integrity sha512-osM9g4UKq4XKimAC7RAXroqi3BXpxfwTswAJQiZdrBjWGFGEyxQrY5H2eDWI8F+MEvEUfYDxA8scqi3QWROCSw== - dependencies: - "@csstools/cascade-layer-name-parser" "^1.0.0" - "@csstools/css-parser-algorithms" "^2.0.0" - "@csstools/css-tokenizer" "^2.0.0" - "@csstools/media-query-list-parser" "^2.0.0" - -postcss-custom-properties@^13.1.0: - version "13.1.4" - resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-13.1.4.tgz#590e5770137011001602806da51243b6c82c1cde" - integrity sha512-iSAdaZrM3KMec8cOSzeTUNXPYDlhqsMJHpt62yrjwG6nAnMtRHPk5JdMzGosBJtqEahDolvD5LNbcq+EZ78o5g== +postcss-custom-media@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz#c8f9637edf45fef761b014c024cee013f80529ea" + integrity sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg== dependencies: - "@csstools/cascade-layer-name-parser" "^1.0.0" - "@csstools/css-parser-algorithms" "^2.0.0" - "@csstools/css-tokenizer" "^2.0.0" postcss-value-parser "^4.2.0" -postcss-custom-properties@^8.0.11: - version "8.0.11" - resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" - integrity sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA== +postcss-custom-properties@^12.1.10: + version "12.1.11" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz#d14bb9b3989ac4d40aaa0e110b43be67ac7845cf" + integrity sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ== dependencies: - postcss "^7.0.17" - postcss-values-parser "^2.0.1" - -postcss-custom-selectors@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" - integrity sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" + postcss-value-parser "^4.2.0" -postcss-custom-selectors@^7.1.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-7.1.2.tgz#e100e9dd628d969a651d7d310cdfd25b27b58d4b" - integrity sha512-jX7VlE3jrgfBIOfxiGNRFq81xUoHSZhvxhQurzE7ZFRv+bUmMwB7/XnA0nNlts2CwNtbXm4Ozy0ZAYKHlCRmBQ== +postcss-custom-selectors@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz#1ab4684d65f30fed175520f82d223db0337239d9" + integrity sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg== dependencies: - "@csstools/cascade-layer-name-parser" "^1.0.0" - "@csstools/css-parser-algorithms" "^2.0.0" - "@csstools/css-tokenizer" "^2.0.0" postcss-selector-parser "^6.0.4" -postcss-dir-pseudo-class@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2" - integrity sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-dir-pseudo-class@^7.0.0: - version "7.0.2" - resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-7.0.2.tgz#71618b7eb4abe067845d11b3c8f322760c9b3e88" - integrity sha512-cMnslilYxBf9k3qejnovrUONZx1rXeUZJw06fgIUBzABJe3D2LiLL5WAER7Imt3nrkaIgG05XZBztueLEf5P8w== +postcss-dir-pseudo-class@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz#2bf31de5de76added44e0a25ecf60ae9f7c7c26c" + integrity sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA== dependencies: postcss-selector-parser "^6.0.10" @@ -8783,94 +8553,49 @@ postcss-discard-overridden@^5.1.0: resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== -postcss-double-position-gradients@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e" - integrity sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA== - dependencies: - postcss "^7.0.5" - postcss-values-parser "^2.0.0" - -postcss-double-position-gradients@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-4.0.2.tgz#855a23201f26be447210504e9b668429cbf4640c" - integrity sha512-GXL1RmFREDK4Q9aYvI2RhVrA6a6qqSMQQ5ke8gSH1xgV6exsqbcJpIumC7AOgooH6/WIG3/K/T8xxAiVHy/tJg== +postcss-double-position-gradients@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz#b96318fdb477be95997e86edd29c6e3557a49b91" + integrity sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ== dependencies: - "@csstools/postcss-progressive-custom-properties" "^2.0.0" + "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" -postcss-env-function@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7" - integrity sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-focus-visible@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" - integrity sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g== - dependencies: - postcss "^7.0.2" - -postcss-focus-visible@^8.0.0: - version "8.0.2" - resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-8.0.2.tgz#a7ac26ffe3e9c2bd17d7200d75e2d79ee8110891" - integrity sha512-f/Vd+EC/GaKElknU59esVcRYr/Y3t1ZAQyL4u2xSOgkDy4bMCmG7VP5cGvj3+BTLNE9ETfEuz2nnt4qkZwTTeA== - dependencies: - postcss-selector-parser "^6.0.10" - -postcss-focus-within@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680" - integrity sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w== +postcss-env-function@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-4.0.6.tgz#7b2d24c812f540ed6eda4c81f6090416722a8e7a" + integrity sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA== dependencies: - postcss "^7.0.2" + postcss-value-parser "^4.2.0" -postcss-focus-within@^7.0.0: - version "7.0.2" - resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-7.0.2.tgz#5d2c866030e66ed22b204c9506de640943310b1c" - integrity sha512-AHAJ89UQBcqBvFgQJE9XasGuwMNkKsGj4D/f9Uk60jFmEBHpAL14DrnSk3Rj+SwZTr/WUG+mh+Rvf8fid/346w== +postcss-focus-visible@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz#50c9ea9afa0ee657fb75635fabad25e18d76bf9e" + integrity sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw== dependencies: - postcss-selector-parser "^6.0.10" + postcss-selector-parser "^6.0.9" -postcss-font-variant@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz#42d4c0ab30894f60f98b17561eb5c0321f502641" - integrity sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA== +postcss-focus-within@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz#5b1d2ec603195f3344b716c0b75f61e44e8d2e20" + integrity sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ== dependencies: - postcss "^7.0.2" + postcss-selector-parser "^6.0.9" postcss-font-variant@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66" integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA== -postcss-gap-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715" - integrity sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg== - dependencies: - postcss "^7.0.2" - -postcss-gap-properties@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-4.0.1.tgz#0347d6a84a46bfbe88bedc542cc4b354e04a8338" - integrity sha512-V5OuQGw4lBumPlwHWk/PRfMKjaq/LTGR4WDTemIMCaMevArVfCCA9wBJiL1VjDAd+rzuCIlkRoRvDsSiAaZ4Fg== - -postcss-image-set-function@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" - integrity sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" +postcss-gap-properties@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz#f7e3cddcf73ee19e94ccf7cb77773f9560aa2fff" + integrity sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg== -postcss-image-set-function@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-5.0.2.tgz#088e0f535f43e74d6ea8033ff7b0482e2735ea6e" - integrity sha512-Sszjwo0ubETX0Fi5MvpYzsONwrsjeabjMoc5YqHvURFItXgIu3HdCjcVuVKGMPGzKRhgaknmdM5uVWInWPJmeg== +postcss-image-set-function@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz#08353bd756f1cbfb3b6e93182c7829879114481f" + integrity sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw== dependencies: postcss-value-parser "^4.2.0" @@ -8899,34 +8624,17 @@ postcss-import@^15.1.0: read-cache "^1.0.0" resolve "^1.1.7" -postcss-initial@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.4.tgz#9d32069a10531fe2ecafa0b6ac750ee0bc7efc53" - integrity sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg== - dependencies: - postcss "^7.0.2" - postcss-initial@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-4.0.1.tgz#529f735f72c5724a0fb30527df6fb7ac54d7de42" integrity sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ== -postcss-lab-function@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" - integrity sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg== - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-lab-function@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-5.1.0.tgz#ed3822feb0f7ff229e6d185929583b16861f7753" - integrity sha512-iZApRTNcpc71uTn7PkzjHtj5cmuZpvu6okX4jHnM5OFi2fG97sodjxkq6SpL65xhW0NviQrAMSX97ntyGVRV0w== +postcss-lab-function@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz#6fe4c015102ff7cd27d1bd5385582f67ebdbdc98" + integrity sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w== dependencies: - "@csstools/color-helpers" "^1.0.0" - "@csstools/postcss-progressive-custom-properties" "^2.0.0" + "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" postcss-loader@^4.3.0: @@ -8940,26 +8648,10 @@ postcss-loader@^4.3.0: schema-utils "^3.0.0" semver "^7.3.4" -postcss-logical@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" - integrity sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA== - dependencies: - postcss "^7.0.2" - -postcss-logical@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-6.1.0.tgz#c33ae75d3edaea7eb821e76dc4e6d0ecedc3200d" - integrity sha512-qb1+LpClhYjxac8SfOcWotnY3unKZesDqIOm+jnGt8rTl7xaIWpE2bPGZHxflOip1E/4ETo79qlJyRL3yrHn1g== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-media-minmax@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" - integrity sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw== - dependencies: - postcss "^7.0.2" +postcss-logical@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-5.0.4.tgz#ec75b1ee54421acc04d5921576b7d8db6b0e6f73" + integrity sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g== postcss-media-minmax@^5.0.0: version "5.0.0" @@ -9044,21 +8736,14 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-nesting@^11.0.0: - version "11.2.1" - resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-11.2.1.tgz#260806f20971c82700d327468b49d1c1b44f1790" - integrity sha512-E6Jq74Jo/PbRAtZioON54NPhUNJYxVWhwxbweYl1vAoBYuGlDIts5yhtKiZFLvkvwT73e/9nFrW3oMqAtgG+GQ== +postcss-nesting@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.2.0.tgz#0b12ce0db8edfd2d8ae0aaf86427370b898890be" + integrity sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA== dependencies: "@csstools/selector-specificity" "^2.0.0" postcss-selector-parser "^6.0.10" -postcss-nesting@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" - integrity sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg== - dependencies: - postcss "^7.0.2" - postcss-normalize-charset@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" @@ -9122,7 +8807,7 @@ postcss-normalize-whitespace@^5.1.1: dependencies: postcss-value-parser "^4.2.0" -postcss-opacity-percentage@^1.1.3: +postcss-opacity-percentage@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz#5b89b35551a556e20c5d23eb5260fbfcf5245da6" integrity sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A== @@ -9135,161 +8820,84 @@ postcss-ordered-values@^5.1.3: cssnano-utils "^3.1.0" postcss-value-parser "^4.2.0" -postcss-overflow-shorthand@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz#31ecf350e9c6f6ddc250a78f0c3e111f32dd4c30" - integrity sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g== - dependencies: - postcss "^7.0.2" - -postcss-overflow-shorthand@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-4.0.1.tgz#cb61ca24d8c4e1dbf14d85181b017cfa6953aa34" - integrity sha512-HQZ0qi/9iSYHW4w3ogNqVNr2J49DHJAl7r8O2p0Meip38jsdnRPgiDW7r/LlLrrMBMe3KHkvNtAV2UmRVxzLIg== +postcss-overflow-shorthand@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz#7ed6486fec44b76f0eab15aa4866cda5d55d893e" + integrity sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A== dependencies: postcss-value-parser "^4.2.0" -postcss-page-break@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf" - integrity sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ== - dependencies: - postcss "^7.0.2" - postcss-page-break@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f" integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ== -postcss-place@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62" - integrity sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-place@^8.0.0: - version "8.0.1" - resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-8.0.1.tgz#408d7a27e99192df51c95fe62a3a34def62aa66a" - integrity sha512-Ow2LedN8sL4pq8ubukO77phSVt4QyCm35ZGCYXKvRFayAwcpgB0sjNJglDoTuRdUL32q/ZC1VkPBo0AOEr4Uiw== +postcss-place@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-7.0.5.tgz#95dbf85fd9656a3a6e60e832b5809914236986c4" + integrity sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g== dependencies: postcss-value-parser "^4.2.0" -postcss-preset-env@^6.7.0: - version "6.7.1" - resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.7.1.tgz#26563d2e9395d626a45a836450844540694bfcef" - integrity sha512-rlRkgX9t0v2On33n7TK8pnkcYOATGQSv48J2RS8GsXhqtg+xk6AummHP88Y5mJo0TLJelBjePvSjScTNkj3+qw== - dependencies: - autoprefixer "^9.6.1" - browserslist "^4.6.4" - caniuse-lite "^1.0.30000981" - css-blank-pseudo "^0.1.4" - css-has-pseudo "^0.10.0" - css-prefers-color-scheme "^3.1.1" - cssdb "^4.4.0" - postcss "^7.0.17" - postcss-attribute-case-insensitive "^4.0.1" - postcss-color-functional-notation "^2.0.1" - postcss-color-gray "^5.0.0" - postcss-color-hex-alpha "^5.0.3" - postcss-color-mod-function "^3.0.3" - postcss-color-rebeccapurple "^4.0.1" - postcss-custom-media "^7.0.8" - postcss-custom-properties "^8.0.11" - postcss-custom-selectors "^5.1.2" - postcss-dir-pseudo-class "^5.0.0" - postcss-double-position-gradients "^1.0.0" - postcss-env-function "^2.0.2" - postcss-focus-visible "^4.0.0" - postcss-focus-within "^3.0.0" - postcss-font-variant "^4.0.0" - postcss-gap-properties "^2.0.0" - postcss-image-set-function "^3.0.1" - postcss-initial "^3.0.0" - postcss-lab-function "^2.0.1" - postcss-logical "^3.0.0" - postcss-media-minmax "^4.0.0" - postcss-nesting "^7.0.0" - postcss-overflow-shorthand "^2.0.0" - postcss-page-break "^2.0.0" - postcss-place "^4.0.1" - postcss-pseudo-class-any-link "^6.0.0" - postcss-replace-overflow-wrap "^3.0.0" - postcss-selector-matches "^4.0.0" - postcss-selector-not "^4.0.0" - -postcss-preset-env@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-8.0.1.tgz#d249c137febc037dd5dbb97e18f94dba4dd0cda8" - integrity sha512-IUbymw0JlUbyVG+I85963PNWgPp3KhnFa1sxU7M/2dGthxV8e297P0VV5W9XcyypoH4hirH2fp1c6fmqh6YnSg== - dependencies: - "@csstools/postcss-cascade-layers" "^3.0.0" - "@csstools/postcss-color-function" "^2.0.0" - "@csstools/postcss-font-format-keywords" "^2.0.0" - "@csstools/postcss-hwb-function" "^2.0.0" - "@csstools/postcss-ic-unit" "^2.0.0" - "@csstools/postcss-is-pseudo-class" "^3.0.0" - "@csstools/postcss-logical-float-and-clear" "^1.0.0" - "@csstools/postcss-logical-resize" "^1.0.0" - "@csstools/postcss-logical-viewport-units" "^1.0.0" - "@csstools/postcss-media-queries-aspect-ratio-number-values" "^1.0.0" - "@csstools/postcss-nested-calc" "^2.0.0" - "@csstools/postcss-normalize-display-values" "^2.0.0" - "@csstools/postcss-oklab-function" "^2.0.0" - "@csstools/postcss-progressive-custom-properties" "^2.0.0" - "@csstools/postcss-scope-pseudo-class" "^2.0.0" - "@csstools/postcss-stepped-value-functions" "^2.0.0" - "@csstools/postcss-text-decoration-shorthand" "^2.0.0" - "@csstools/postcss-trigonometric-functions" "^2.0.0" - "@csstools/postcss-unset-value" "^2.0.0" +postcss-preset-env@^6.7.0, postcss-preset-env@^7.0.0, postcss-preset-env@^8.0.1: + version "7.8.3" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-7.8.3.tgz#2a50f5e612c3149cc7af75634e202a5b2ad4f1e2" + integrity sha512-T1LgRm5uEVFSEF83vHZJV2z19lHg4yJuZ6gXZZkqVsqv63nlr6zabMH3l4Pc01FQCyfWVrh2GaUeCVy9Po+Aag== + dependencies: + "@csstools/postcss-cascade-layers" "^1.1.1" + "@csstools/postcss-color-function" "^1.1.1" + "@csstools/postcss-font-format-keywords" "^1.0.1" + "@csstools/postcss-hwb-function" "^1.0.2" + "@csstools/postcss-ic-unit" "^1.0.1" + "@csstools/postcss-is-pseudo-class" "^2.0.7" + "@csstools/postcss-nested-calc" "^1.0.0" + "@csstools/postcss-normalize-display-values" "^1.0.1" + "@csstools/postcss-oklab-function" "^1.1.1" + "@csstools/postcss-progressive-custom-properties" "^1.3.0" + "@csstools/postcss-stepped-value-functions" "^1.0.1" + "@csstools/postcss-text-decoration-shorthand" "^1.0.0" + "@csstools/postcss-trigonometric-functions" "^1.0.2" + "@csstools/postcss-unset-value" "^1.0.2" autoprefixer "^10.4.13" browserslist "^4.21.4" - css-blank-pseudo "^5.0.0" - css-has-pseudo "^5.0.0" - css-prefers-color-scheme "^8.0.0" - cssdb "^7.4.0" - postcss-attribute-case-insensitive "^6.0.0" + css-blank-pseudo "^3.0.3" + css-has-pseudo "^3.0.4" + css-prefers-color-scheme "^6.0.3" + cssdb "^7.1.0" + postcss-attribute-case-insensitive "^5.0.2" postcss-clamp "^4.1.0" - postcss-color-functional-notation "^5.0.0" - postcss-color-hex-alpha "^9.0.0" - postcss-color-rebeccapurple "^8.0.0" - postcss-custom-media "^9.1.0" - postcss-custom-properties "^13.1.0" - postcss-custom-selectors "^7.1.0" - postcss-dir-pseudo-class "^7.0.0" - postcss-double-position-gradients "^4.0.0" - postcss-focus-visible "^8.0.0" - postcss-focus-within "^7.0.0" + postcss-color-functional-notation "^4.2.4" + postcss-color-hex-alpha "^8.0.4" + postcss-color-rebeccapurple "^7.1.1" + postcss-custom-media "^8.0.2" + postcss-custom-properties "^12.1.10" + postcss-custom-selectors "^6.0.3" + postcss-dir-pseudo-class "^6.0.5" + postcss-double-position-gradients "^3.1.2" + postcss-env-function "^4.0.6" + postcss-focus-visible "^6.0.4" + postcss-focus-within "^5.0.4" postcss-font-variant "^5.0.0" - postcss-gap-properties "^4.0.0" - postcss-image-set-function "^5.0.0" + postcss-gap-properties "^3.0.5" + postcss-image-set-function "^4.0.7" postcss-initial "^4.0.1" - postcss-lab-function "^5.0.0" - postcss-logical "^6.0.0" + postcss-lab-function "^4.2.1" + postcss-logical "^5.0.4" postcss-media-minmax "^5.0.0" - postcss-nesting "^11.0.0" - postcss-opacity-percentage "^1.1.3" - postcss-overflow-shorthand "^4.0.0" + postcss-nesting "^10.2.0" + postcss-opacity-percentage "^1.1.2" + postcss-overflow-shorthand "^3.0.4" postcss-page-break "^3.0.4" - postcss-place "^8.0.0" - postcss-pseudo-class-any-link "^8.0.0" + postcss-place "^7.0.5" + postcss-pseudo-class-any-link "^7.1.6" postcss-replace-overflow-wrap "^4.0.0" - postcss-selector-not "^7.0.0" + postcss-selector-not "^6.0.1" postcss-value-parser "^4.2.0" -postcss-pseudo-class-any-link@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" - integrity sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-pseudo-class-any-link@^8.0.0: - version "8.0.2" - resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-8.0.2.tgz#f5738503f2045de0c4dc216eca99bd835f74e42e" - integrity sha512-FYTIuRE07jZ2CW8POvctRgArQJ43yxhr5vLmImdKUvjFCkR09kh8pIdlCwdx/jbFm7MiW4QP58L4oOUv3grQYA== +postcss-pseudo-class-any-link@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz#2693b221902da772c278def85a4d9a64b6e617ab" + integrity sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w== dependencies: postcss-selector-parser "^6.0.10" @@ -9308,50 +8916,18 @@ postcss-reduce-transforms@^5.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-replace-overflow-wrap@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" - integrity sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== - dependencies: - postcss "^7.0.2" - postcss-replace-overflow-wrap@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== -postcss-selector-matches@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" - integrity sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww== - dependencies: - balanced-match "^1.0.0" - postcss "^7.0.2" - -postcss-selector-not@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz#263016eef1cf219e0ade9a913780fc1f48204cbf" - integrity sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ== - dependencies: - balanced-match "^1.0.0" - postcss "^7.0.2" - -postcss-selector-not@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-7.0.1.tgz#8142e90c8eb6c8c5faecb3e9d96d4353d02e94fb" - integrity sha512-1zT5C27b/zeJhchN7fP0kBr16Cc61mu7Si9uWWLoA3Px/D9tIJPKchJCkUH3tPO5D0pCFmGeApAv8XpXBQJ8SQ== +postcss-selector-not@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz#8f0a709bf7d4b45222793fc34409be407537556d" + integrity sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ== dependencies: postcss-selector-parser "^6.0.10" -postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" - integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== - dependencies: - cssesc "^2.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: version "6.0.11" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" @@ -9390,16 +8966,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" - integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== - dependencies: - flatten "^1.0.2" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.32, postcss@^7.0.36, postcss@^7.0.5, postcss@^7.0.6: +postcss@^7.0.36: version "7.0.39" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== @@ -11061,10 +10628,10 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== -ufo@^0.7.9: - version "0.7.11" - resolved "https://registry.yarnpkg.com/ufo/-/ufo-0.7.11.tgz#17defad497981290383c5d26357773431fdbadcb" - integrity sha512-IT3q0lPvtkqQ8toHQN/BkOi4VIqoqheqM1FnkNWT9y0G8B3xJhwnoKBu5OHx8zHDOvveQzfKuFowJ0VSARiIDg== +ufo@0.7.9, ufo@^0.7.9: + version "0.7.9" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-0.7.9.tgz#0268e3734b413c9ed6f3510201f42372821b875c" + integrity sha512-6t9LrLk3FhqTS+GW3IqlITtfRB5JAVr5MMNjpBECfK827W+Vh5Ilw/LhTcHWrt6b3hkeBvcbjx4Ti7QVFzmcww== ufo@^0.8.5: version "0.8.6" @@ -11156,11 +10723,6 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA== - unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" From fe17922bb8038a69fd754c5fbec43756056fe2d5 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Tue, 25 Apr 2023 12:46:00 -0500 Subject: [PATCH 2/3] Feature: Global Timeline (#2265) * extended query filter to accept nested tables * decoupled timeline api from recipe slug * modified frontend to use simplified events api * fixed nested loop index ghosting * updated existing tests * gave mypy a snack * added tests for nested queries * fixed "last made" render error * decoupled recipe timeline from dialog * removed unused props * tweaked recipe get_all to accept ids * created group global timeline added new timeline page to sidebar reformatted the recipe timeline added vertical option to recipe card mobile * extracted timeline item into its own component * fixed apploader centering * added paginated scrolling to recipe timeline * added sort direction config fixed infinite scroll on dialog fixed hasMore var not resetting during instantiation * added sort direction to user preferences * updated API docs with new query filter feature * better error tracing * fix for recipe not found response * simplified recipe crud route for slug/id added test for fetching by slug/id * made query filter UUID validation clearer * moved timeline menu option below shopping lists --------- Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> --- .../getting-started/api-usage.md | 7 + .../Domain/Recipe/RecipeCardMobile.vue | 20 +- .../Domain/Recipe/RecipeDialogTimeline.vue | 245 ---------------- .../Domain/Recipe/RecipeLastMade.vue | 16 +- .../RecipePageParts/RecipePageHeader.vue | 2 +- .../Domain/Recipe/RecipeTimeline.vue | 266 ++++++++++++++++++ .../Domain/Recipe/RecipeTimelineBadge.vue | 28 +- .../Recipe/RecipeTimelineContextMenu.vue | 4 - .../Domain/Recipe/RecipeTimelineItem.vue | 162 +++++++++++ frontend/components/global/AppLoader.vue | 40 +-- frontend/composables/use-users/preferences.ts | 18 ++ frontend/lang/messages/en-US.json | 2 + frontend/layouts/default.vue | 6 + frontend/lib/api/types/recipe.ts | 1 + frontend/lib/api/user/recipes/recipe.ts | 21 +- frontend/pages/group/timeline.vue | 52 ++++ mealie/routes/recipe/recipe_crud_routes.py | 21 +- mealie/routes/recipe/timeline_events.py | 117 +++----- .../schema/recipe/recipe_timeline_events.py | 2 +- mealie/schema/response/query_filter.py | 49 +++- mealie/services/recipe/recipe_service.py | 19 +- .../scheduler/tasks/create_timeline_events.py | 5 +- .../test_recipe_migrations.py | 6 +- .../user_recipe_tests/test_recipe_crud.py | 27 ++ .../test_recipe_timeline_events.py | 106 ++----- .../repository_tests/test_pagination.py | 66 ++++- .../tasks/test_create_timeline_events.py | 54 ++-- tests/utils/api_routes/__init__.py | 15 +- 28 files changed, 871 insertions(+), 506 deletions(-) delete mode 100644 frontend/components/Domain/Recipe/RecipeDialogTimeline.vue create mode 100644 frontend/components/Domain/Recipe/RecipeTimeline.vue create mode 100644 frontend/components/Domain/Recipe/RecipeTimelineItem.vue create mode 100644 frontend/pages/group/timeline.vue diff --git a/docs/docs/documentation/getting-started/api-usage.md b/docs/docs/documentation/getting-started/api-usage.md index 734fe256799..0930d33bcaf 100644 --- a/docs/docs/documentation/getting-started/api-usage.md +++ b/docs/docs/documentation/getting-started/api-usage.md @@ -72,6 +72,13 @@ This filter will find all recipes created on or after a particular date:
This filter will find all units that have `useAbbreviation` disabled:
`useAbbreviation = false` +##### Nested Property filters +When querying tables with relationships, you can filter properties on related tables. For instance, if you want to query all recipes owned by a particular user:
+`user.username = "SousChef20220320"` + +This timeline event filter will return all timeline events for recipes that were created after a particular date:
+`recipe.createdAt >= "2023-02-25"` + ##### Compound Filters You can combine multiple filter statements using logical operators (`AND`, `OR`). diff --git a/frontend/components/Domain/Recipe/RecipeCardMobile.vue b/frontend/components/Domain/Recipe/RecipeCardMobile.vue index c38b65f4bfa..c729b874c87 100644 --- a/frontend/components/Domain/Recipe/RecipeCardMobile.vue +++ b/frontend/components/Domain/Recipe/RecipeCardMobile.vue @@ -7,8 +7,18 @@ :to="$listeners.selected ? undefined : `/recipe/${slug}`" @click="$emit('selected')" > + + + - + + /> @@ -25,7 +35,7 @@ -
+
- - - - - - - - - - - - - - - {{ $globals.icons.calendar }} - {{ new Date(event.timestamp+"Z").toLocaleDateString($i18n.locale) }} - - - - {{ event.subject }} - - - - - - - - - - - {{ event.subject }} -
- {{ event.eventMessage }} -
-
-
-
-
-
-
-
-
- - - {{ $t("recipe.timeline-is-empty") }} - - -
- - - diff --git a/frontend/components/Domain/Recipe/RecipeLastMade.vue b/frontend/components/Domain/Recipe/RecipeLastMade.vue index a30912d8c19..5e086355251 100644 --- a/frontend/components/Domain/Recipe/RecipeLastMade.vue +++ b/frontend/components/Domain/Recipe/RecipeLastMade.vue @@ -75,7 +75,7 @@ import { defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/comp import { whenever } from "@vueuse/core"; import { VForm } from "~/types/vuetify"; import { useUserApi } from "~/composables/api"; -import { RecipeTimelineEventIn } from "~/lib/api/types/recipe"; +import { Recipe, RecipeTimelineEventIn } from "~/lib/api/types/recipe"; export default defineComponent({ props: { @@ -83,9 +83,9 @@ export default defineComponent({ type: String, default: null, }, - recipeSlug: { - type: String, - required: true, + recipe: { + type: Object as () => Recipe, + default: null, }, }, setup(props, context) { @@ -99,6 +99,7 @@ export default defineComponent({ eventType: "comment", eventMessage: "", timestamp: undefined, + recipeId: props.recipe?.id || "", }); whenever( @@ -113,20 +114,21 @@ export default defineComponent({ const state = reactive({datePickerMenu: false}); async function createTimelineEvent() { - if (!newTimelineEvent.value.timestamp) { + if (!(newTimelineEvent.value.timestamp && props.recipe?.id && props.recipe?.slug)) { return; } + newTimelineEvent.value.recipeId = props.recipe.id const actions: Promise[] = []; // the user only selects the date, so we set the time to end of day local time // we choose the end of day so it always comes after "new recipe" events newTimelineEvent.value.timestamp = new Date(newTimelineEvent.value.timestamp + "T23:59:59").toISOString(); - actions.push(userApi.recipes.createTimelineEvent(props.recipeSlug, newTimelineEvent.value)); + actions.push(userApi.recipes.createTimelineEvent(newTimelineEvent.value)); // we also update the recipe's last made value if (!props.value || newTimelineEvent.value.timestamp > props.value) { - actions.push(userApi.recipes.updateLastMade(props.recipeSlug, newTimelineEvent.value.timestamp)); + actions.push(userApi.recipes.updateLastMade(props.recipe.slug, newTimelineEvent.value.timestamp)); // update recipe in parent so the user can see it // we remove the trailing "Z" since this is how the API returns it diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageHeader.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageHeader.vue index c433230e3c8..0754a13b3a1 100644 --- a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageHeader.vue +++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageHeader.vue @@ -13,7 +13,7 @@
diff --git a/frontend/components/Domain/Recipe/RecipeTimeline.vue b/frontend/components/Domain/Recipe/RecipeTimeline.vue new file mode 100644 index 00000000000..bcd854c6ab1 --- /dev/null +++ b/frontend/components/Domain/Recipe/RecipeTimeline.vue @@ -0,0 +1,266 @@ + + + diff --git a/frontend/components/Domain/Recipe/RecipeTimelineBadge.vue b/frontend/components/Domain/Recipe/RecipeTimelineBadge.vue index d699f0ff396..9fd03d0eb83 100644 --- a/frontend/components/Domain/Recipe/RecipeTimelineBadge.vue +++ b/frontend/components/Domain/Recipe/RecipeTimelineBadge.vue @@ -14,17 +14,20 @@ {{ $globals.icons.timelineText }} - + + + + {{ $t('recipe.open-timeline') }} diff --git a/frontend/components/Domain/Recipe/RecipeTimelineContextMenu.vue b/frontend/components/Domain/Recipe/RecipeTimelineContextMenu.vue index cbd22810550..501c44819f3 100644 --- a/frontend/components/Domain/Recipe/RecipeTimelineContextMenu.vue +++ b/frontend/components/Domain/Recipe/RecipeTimelineContextMenu.vue @@ -113,10 +113,6 @@ export default defineComponent({ type: String, default: "primary", }, - slug: { - type: String, - required: true, - }, event: { type: Object as () => RecipeTimelineEventOut, required: true, diff --git a/frontend/components/Domain/Recipe/RecipeTimelineItem.vue b/frontend/components/Domain/Recipe/RecipeTimelineItem.vue new file mode 100644 index 00000000000..f5674607cfd --- /dev/null +++ b/frontend/components/Domain/Recipe/RecipeTimelineItem.vue @@ -0,0 +1,162 @@ + + + diff --git a/frontend/components/global/AppLoader.vue b/frontend/components/global/AppLoader.vue index 34913a249b6..c7ec8e7bc27 100644 --- a/frontend/components/global/AppLoader.vue +++ b/frontend/components/global/AppLoader.vue @@ -1,21 +1,23 @@ @@ -41,6 +43,10 @@ export default defineComponent({ type: Boolean, default: false, }, + waitingText: { + type: String, + default: undefined, + } }, setup(props) { const size = computed(() => { @@ -65,11 +71,11 @@ export default defineComponent({ }); const { i18n } = useContext(); - const waitingText = i18n.t("general.loading-recipes"); + const waitingTextCalculated = props.waitingText == null ? i18n.t("general.loading-recipes") : props.waitingText; return { size, - waitingText, + waitingTextCalculated, }; }, }); diff --git a/frontend/composables/use-users/preferences.ts b/frontend/composables/use-users/preferences.ts index 471e2482e47..c93c5dcb4c0 100644 --- a/frontend/composables/use-users/preferences.ts +++ b/frontend/composables/use-users/preferences.ts @@ -25,6 +25,10 @@ export interface UserShoppingListPreferences { viewByLabel: boolean; } +export interface UserTimelinePreferences { + orderDirection: string; +} + export function useUserPrintPreferences(): Ref { const fromStorage = useLocalStorage( "recipe-print-preferences", @@ -75,3 +79,17 @@ export function useShoppingListPreferences(): Ref { return fromStorage; } + +export function useTimelinePreferences(): Ref { + const fromStorage = useLocalStorage( + "timeline-preferences", + { + orderDirection: "asc", + }, + { mergeDefaults: true } + // we cast to a Ref because by default it will return an optional type ref + // but since we pass defaults we know all properties are set. + ) as unknown as Ref; + + return fromStorage; +} diff --git a/frontend/lang/messages/en-US.json b/frontend/lang/messages/en-US.json index 5e240f8257c..569d95d7edb 100644 --- a/frontend/lang/messages/en-US.json +++ b/frontend/lang/messages/en-US.json @@ -113,6 +113,7 @@ "json": "JSON", "keyword": "Keyword", "link-copied": "Link Copied", + "loading-events": "Loading Events", "loading-recipes": "Loading Recipes", "message": "Message", "monday": "Monday", @@ -478,6 +479,7 @@ "edit-timeline-event": "Edit Timeline Event", "timeline": "Timeline", "timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!", + "group-global-timeline": "{groupName} Global Timeline", "open-timeline": "Open Timeline", "made-this": "I Made This", "how-did-it-turn-out": "How did it turn out?", diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue index 77ae6490ace..5d3befd2fae 100644 --- a/frontend/layouts/default.vue +++ b/frontend/layouts/default.vue @@ -169,6 +169,12 @@ export default defineComponent({ to: "/shopping-lists", restricted: true, }, + { + icon: $globals.icons.timelineText, + title: i18n.t("recipe.timeline"), + to: "/group/timeline", + restricted: true, + }, { icon: $globals.icons.tags, to: "/recipes/categories", diff --git a/frontend/lib/api/types/recipe.ts b/frontend/lib/api/types/recipe.ts index d5e8e61b181..e99b691d767 100644 --- a/frontend/lib/api/types/recipe.ts +++ b/frontend/lib/api/types/recipe.ts @@ -355,6 +355,7 @@ export interface RecipeTimelineEventIn { eventMessage?: string; image?: string; timestamp?: string; + recipeId: string; } export interface RecipeTimelineEventOut { userId: string; diff --git a/frontend/lib/api/user/recipes/recipe.ts b/frontend/lib/api/user/recipes/recipe.ts index e397ec12dd4..5499fd98b65 100644 --- a/frontend/lib/api/user/recipes/recipe.ts +++ b/frontend/lib/api/user/recipes/recipe.ts @@ -39,6 +39,7 @@ const routes = { recipesParseIngredient: `${prefix}/parser/ingredient`, recipesParseIngredients: `${prefix}/parser/ingredients`, recipesCreateFromOcr: `${prefix}/recipes/create-ocr`, + recipesTimelineEvent: `${prefix}/recipes/timeline/events`, recipesRecipeSlug: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}`, recipesRecipeSlugExport: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/exports`, @@ -50,9 +51,7 @@ const routes = { recipesSlugCommentsId: (slug: string, id: number) => `${prefix}/recipes/${slug}/comments/${id}`, recipesSlugLastMade: (slug: string) => `${prefix}/recipes/${slug}/last-made`, - - recipesSlugTimelineEvent: (slug: string) => `${prefix}/recipes/${slug}/timeline/events`, - recipesSlugTimelineEventId: (slug: string, id: string) => `${prefix}/recipes/${slug}/timeline/events/${id}`, + recipesTimelineEventId: (id: string) => `${prefix}/recipes/timeline/events/${id}`, }; export type RecipeSearchQuery = { @@ -170,24 +169,24 @@ export class RecipeAPI extends BaseCRUDAPI { return await this.requests.patch(routes.recipesSlugLastMade(recipeSlug), { timestamp }) } - async createTimelineEvent(recipeSlug: string, payload: RecipeTimelineEventIn) { - return await this.requests.post(routes.recipesSlugTimelineEvent(recipeSlug), payload); + async createTimelineEvent(payload: RecipeTimelineEventIn) { + return await this.requests.post(routes.recipesTimelineEvent, payload); } - async updateTimelineEvent(recipeSlug: string, eventId: string, payload: RecipeTimelineEventUpdate) { + async updateTimelineEvent(eventId: string, payload: RecipeTimelineEventUpdate) { return await this.requests.put( - routes.recipesSlugTimelineEventId(recipeSlug, eventId), + routes.recipesTimelineEventId(eventId), payload ); } - async deleteTimelineEvent(recipeSlug: string, eventId: string) { - return await this.requests.delete(routes.recipesSlugTimelineEventId(recipeSlug, eventId)); + async deleteTimelineEvent(eventId: string) { + return await this.requests.delete(routes.recipesTimelineEventId(eventId)); } - async getAllTimelineEvents(recipeSlug: string, page = 1, perPage = -1, params = {} as any) { + async getAllTimelineEvents(page = 1, perPage = -1, params = {} as any) { return await this.requests.get>( - routes.recipesSlugTimelineEvent(recipeSlug), + routes.recipesTimelineEvent, { params: { page, perPage, ...params }, } diff --git a/frontend/pages/group/timeline.vue b/frontend/pages/group/timeline.vue new file mode 100644 index 00000000000..a8c14d5094d --- /dev/null +++ b/frontend/pages/group/timeline.vue @@ -0,0 +1,52 @@ + + + diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index a7288856302..ce3538b7f54 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -4,7 +4,7 @@ import orjson import sqlalchemy -from fastapi import BackgroundTasks, Depends, File, Form, HTTPException, Query, Request, status +from fastapi import BackgroundTasks, Depends, File, Form, HTTPException, Path, Query, Request, status from fastapi.datastructures import UploadFile from fastapi.responses import JSONResponse from pydantic import UUID4, BaseModel, Field @@ -24,12 +24,7 @@ from mealie.routes._base.routers import MealieCrudRoute, UserAPIRouter from mealie.schema.cookbook.cookbook import ReadCookBook from mealie.schema.recipe import Recipe, RecipeImageTypes, ScrapeRecipe -from mealie.schema.recipe.recipe import ( - CreateRecipe, - CreateRecipeByUrlBulk, - RecipeLastMade, - RecipeSummary, -) +from mealie.schema.recipe.recipe import CreateRecipe, CreateRecipeByUrlBulk, RecipeLastMade, RecipeSummary from mealie.schema.recipe.recipe_asset import RecipeAsset from mealie.schema.recipe.recipe_ingredient import RecipeIngredient from mealie.schema.recipe.recipe_scraper import ScrapeRecipeTest @@ -284,9 +279,15 @@ def get_all( return JSONBytes(content=json_compatible_response) @router.get("/{slug}", response_model=Recipe) - def get_one(self, slug: str): - """Takes in a recipe slug, returns all data for a recipe""" - return self.mixins.get_one(slug) + def get_one(self, slug: str = Path(..., description="A recipe's slug or id")): + """Takes in a recipe's slug or id and returns all data for a recipe""" + try: + recipe = self.service.get_one_by_slug_or_id(slug) + except Exception as e: + self.handle_exceptions(e) + return None + + return recipe @router.post("", status_code=201, response_model=str) def create_one(self, data: CreateRecipe) -> str | None: diff --git a/mealie/routes/recipe/timeline_events.py b/mealie/routes/recipe/timeline_events.py index 445112716c8..c6d820ac6b1 100644 --- a/mealie/routes/recipe/timeline_events.py +++ b/mealie/routes/recipe/timeline_events.py @@ -6,7 +6,6 @@ from mealie.routes._base import BaseCrudController, controller from mealie.routes._base.mixins import HttpRepo from mealie.routes._base.routers import MealieCrudRoute, UserAPIRouter -from mealie.schema.recipe.recipe import Recipe from mealie.schema.recipe.recipe_timeline_events import ( RecipeTimelineEventCreate, RecipeTimelineEventIn, @@ -18,7 +17,7 @@ from mealie.services import urls from mealie.services.event_bus_service.event_types import EventOperation, EventRecipeTimelineEventData, EventTypes -events_router = UserAPIRouter(route_class=MealieCrudRoute, prefix="/{slug}/timeline/events") +events_router = UserAPIRouter(route_class=MealieCrudRoute, prefix="/timeline/events") @controller(events_router) @@ -27,6 +26,10 @@ class RecipeTimelineEventsController(BaseCrudController): def repo(self): return self.repos.recipe_timeline_events + @cached_property + def recipes_repo(self): + return self.repos.recipes.by_group(self.group_id) + @cached_property def mixins(self): return HttpRepo[RecipeTimelineEventCreate, RecipeTimelineEventOut, RecipeTimelineEventUpdate]( @@ -35,39 +38,26 @@ def mixins(self): self.registered_exceptions, ) - def get_recipe_from_slug(self, slug: str) -> Recipe: - recipe = self.repos.recipes.by_group(self.group_id).get_one(slug) - if not recipe or self.group_id != recipe.group_id: - raise HTTPException(status_code=404, detail="recipe not found") - - return recipe - @events_router.get("", response_model=RecipeTimelineEventPagination) - def get_all(self, slug: str, q: PaginationQuery = Depends(PaginationQuery)): - recipe = self.get_recipe_from_slug(slug) - recipe_filter = f"recipe_id = {recipe.id}" - - if q.query_filter: - q.query_filter = f"({q.query_filter}) AND {recipe_filter}" - - else: - q.query_filter = recipe_filter - + def get_all(self, q: PaginationQuery = Depends(PaginationQuery)): response = self.repo.page_all( pagination=q, override=RecipeTimelineEventOut, ) - response.set_pagination_guides(events_router.url_path_for("get_all", slug=slug), q.dict()) + response.set_pagination_guides(events_router.url_path_for("get_all"), q.dict()) return response @events_router.post("", response_model=RecipeTimelineEventOut, status_code=201) - def create_one(self, slug: str, data: RecipeTimelineEventIn): + def create_one(self, data: RecipeTimelineEventIn): # if the user id is not specified, use the currently-authenticated user data.user_id = data.user_id or self.user.id - recipe = self.get_recipe_from_slug(slug) - event_data = data.cast(RecipeTimelineEventCreate, recipe_id=recipe.id) + recipe = self.recipes_repo.get_one(data.recipe_id, "id") + if not recipe: + raise HTTPException(status_code=404, detail="recipe not found") + + event_data = data.cast(RecipeTimelineEventCreate) event = self.mixins.create_one(event_data) self.publish_event( @@ -78,69 +68,50 @@ def create_one(self, slug: str, data: RecipeTimelineEventIn): message=self.t( "notifications.generic-updated-with-url", name=recipe.name, - url=urls.recipe_url(slug, self.settings.BASE_URL), + url=urls.recipe_url(recipe.slug, self.settings.BASE_URL), ), ) return event @events_router.get("/{item_id}", response_model=RecipeTimelineEventOut) - def get_one(self, slug: str, item_id: UUID4): - recipe = self.get_recipe_from_slug(slug) - event = self.mixins.get_one(item_id) - - # validate that this event belongs to the given recipe slug - if event.recipe_id != recipe.id: - raise HTTPException(status_code=404, detail="recipe event not found") - - return event + def get_one(self, item_id: UUID4): + return self.mixins.get_one(item_id) @events_router.put("/{item_id}", response_model=RecipeTimelineEventOut) - def update_one(self, slug: str, item_id: UUID4, data: RecipeTimelineEventUpdate): - recipe = self.get_recipe_from_slug(slug) - event = self.mixins.get_one(item_id) - - # validate that this event belongs to the given recipe slug - if event.recipe_id != recipe.id: - raise HTTPException(status_code=404, detail="recipe event not found") - + def update_one(self, item_id: UUID4, data: RecipeTimelineEventUpdate): event = self.mixins.update_one(data, item_id) - - self.publish_event( - event_type=EventTypes.recipe_updated, - document_data=EventRecipeTimelineEventData( - operation=EventOperation.update, recipe_slug=recipe.slug, recipe_timeline_event_id=event.id - ), - message=self.t( - "notifications.generic-updated-with-url", - name=recipe.name, - url=urls.recipe_url(slug, self.settings.BASE_URL), - ), - ) + recipe = self.recipes_repo.get_one(event.recipe_id, "id") + if recipe: + self.publish_event( + event_type=EventTypes.recipe_updated, + document_data=EventRecipeTimelineEventData( + operation=EventOperation.update, recipe_slug=recipe.slug, recipe_timeline_event_id=event.id + ), + message=self.t( + "notifications.generic-updated-with-url", + name=recipe.name, + url=urls.recipe_url(recipe.slug, self.settings.BASE_URL), + ), + ) return event @events_router.delete("/{item_id}", response_model=RecipeTimelineEventOut) - def delete_one(self, slug: str, item_id: UUID4): - recipe = self.get_recipe_from_slug(slug) - event = self.mixins.get_one(item_id) - - # validate that this event belongs to the given recipe slug - if event.recipe_id != recipe.id: - raise HTTPException(status_code=404, detail="recipe event not found") - + def delete_one(self, item_id: UUID4): event = self.mixins.delete_one(item_id) - - self.publish_event( - event_type=EventTypes.recipe_updated, - document_data=EventRecipeTimelineEventData( - operation=EventOperation.delete, recipe_slug=recipe.slug, recipe_timeline_event_id=event.id - ), - message=self.t( - "notifications.generic-updated-with-url", - name=recipe.name, - url=urls.recipe_url(slug, self.settings.BASE_URL), - ), - ) + recipe = self.recipes_repo.get_one(event.recipe_id, "id") + if recipe: + self.publish_event( + event_type=EventTypes.recipe_updated, + document_data=EventRecipeTimelineEventData( + operation=EventOperation.delete, recipe_slug=recipe.slug, recipe_timeline_event_id=event.id + ), + message=self.t( + "notifications.generic-updated-with-url", + name=recipe.name, + url=urls.recipe_url(recipe.slug, self.settings.BASE_URL), + ), + ) return event diff --git a/mealie/schema/recipe/recipe_timeline_events.py b/mealie/schema/recipe/recipe_timeline_events.py index f82466720a7..8789432dc3d 100644 --- a/mealie/schema/recipe/recipe_timeline_events.py +++ b/mealie/schema/recipe/recipe_timeline_events.py @@ -14,6 +14,7 @@ class TimelineEventType(Enum): class RecipeTimelineEventIn(MealieModel): + recipe_id: UUID4 user_id: UUID4 | None = None """can be inferred in some contexts, so it's not required""" @@ -30,7 +31,6 @@ class Config: class RecipeTimelineEventCreate(RecipeTimelineEventIn): - recipe_id: UUID4 user_id: UUID4 diff --git a/mealie/schema/response/query_filter.py b/mealie/schema/response/query_filter.py index e62e74ae64f..0f4704fe24a 100644 --- a/mealie/schema/response/query_filter.py +++ b/mealie/schema/response/query_filter.py @@ -4,14 +4,18 @@ import re from enum import Enum from typing import Any, TypeVar, cast +from uuid import UUID from dateutil import parser as date_parser from dateutil.parser import ParserError from humps import decamelize -from sqlalchemy import Select, bindparam, text +from sqlalchemy import Select, bindparam, inspect, text +from sqlalchemy.orm import Mapper from sqlalchemy.sql import sqltypes from sqlalchemy.sql.expression import BindParameter +from mealie.db.models._model_utils.guid import GUID + Model = TypeVar("Model") @@ -87,14 +91,51 @@ def filter_query(self, query: Select, model: type[Model]) -> Select: # we explicitly mark this as a filter component instead cast doesn't # actually do anything at runtime component = cast(QueryFilterComponent, component) + attribute_chain = component.attribute_name.split(".") + if not attribute_chain: + raise ValueError("invalid query string: attribute name cannot be empty") + + attr_model: Any = model + for j, attribute_link in enumerate(attribute_chain): + # last element + if j == len(attribute_chain) - 1: + if not hasattr(attr_model, attribute_link): + raise ValueError( + f"invalid query string: '{component.attribute_name}' does not exist on this schema" + ) + + attr_value = attribute_link + if j: + # use the nested table name, rather than the dot notation + component.attribute_name = f"{attr_model.__table__.name}.{attr_value}" - if not hasattr(model, component.attribute_name): - raise ValueError(f"invalid query string: '{component.attribute_name}' does not exist on this schema") + continue + + # join on nested model + try: + query = query.join(getattr(attr_model, attribute_link)) + + mapper: Mapper = inspect(attr_model) + relationship = mapper.relationships[attribute_link] + attr_model = relationship.mapper.class_ + + except (AttributeError, KeyError) as e: + raise ValueError( + f"invalid query string: '{component.attribute_name}' does not exist on this schema" + ) from e # convert values to their proper types - attr = getattr(model, component.attribute_name) + attr = getattr(attr_model, attr_value) value: Any = component.value + if isinstance(attr.type, (GUID)): + try: + # we don't set value since a UUID is functionally identical to a string here + UUID(value) + + except ValueError as e: + raise ValueError(f"invalid query string: invalid UUID '{component.value}'") from e + if isinstance(attr.type, (sqltypes.Date, sqltypes.DateTime)): # TODO: add support for IS NULL and IS NOT NULL # in the meantime, this will work for the specific usecase of non-null dates/datetimes diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index 08df0c06cfa..53138b73be8 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -3,7 +3,7 @@ from datetime import datetime from pathlib import Path from shutil import copytree, rmtree -from uuid import uuid4 +from uuid import UUID, uuid4 from zipfile import ZipFile from fastapi import UploadFile @@ -42,8 +42,8 @@ def __init__(self, repos: AllRepositories, user: PrivateUser, group: GroupInDB): self.group = group super().__init__() - def _get_recipe(self, slug: str) -> Recipe: - recipe = self.repos.recipes.by_group(self.group.id).get_one(slug) + def _get_recipe(self, data: str | UUID, key: str | None = None) -> Recipe: + recipe = self.repos.recipes.by_group(self.group.id).get_one(data, key) if recipe is None: raise exceptions.NoEntryFound("Recipe not found.") return recipe @@ -107,6 +107,19 @@ def _recipe_creation_factory(user: PrivateUser, name: str, additional_attrs: dic return Recipe(**additional_attrs) + def get_one_by_slug_or_id(self, slug_or_id: str | UUID) -> Recipe | None: + if isinstance(slug_or_id, str): + try: + slug_or_id = UUID(slug_or_id) + except ValueError: + pass + + if isinstance(slug_or_id, UUID): + return self._get_recipe(slug_or_id, "id") + + else: + return self._get_recipe(slug_or_id, "slug") + def create_one(self, create_data: Recipe | CreateRecipe) -> Recipe: if create_data.name is None: create_data.name = "New Recipe" diff --git a/mealie/services/scheduler/tasks/create_timeline_events.py b/mealie/services/scheduler/tasks/create_timeline_events.py index c5e62375630..2d7d7213544 100644 --- a/mealie/services/scheduler/tasks/create_timeline_events.py +++ b/mealie/services/scheduler/tasks/create_timeline_events.py @@ -6,10 +6,7 @@ from mealie.repos.all_repositories import get_repositories from mealie.schema.meal_plan.new_meal import PlanEntryType from mealie.schema.recipe.recipe import RecipeSummary -from mealie.schema.recipe.recipe_timeline_events import ( - RecipeTimelineEventCreate, - TimelineEventType, -) +from mealie.schema.recipe.recipe_timeline_events import RecipeTimelineEventCreate, TimelineEventType from mealie.schema.response.pagination import PaginationQuery from mealie.schema.user.user import DEFAULT_INTEGRATION_ID from mealie.services.event_bus_service.event_bus_service import EventBusService diff --git a/tests/integration_tests/recipe_migration_tests/test_recipe_migrations.py b/tests/integration_tests/recipe_migration_tests/test_recipe_migrations.py index 6c5306c660e..94332149b6d 100644 --- a/tests/integration_tests/recipe_migration_tests/test_recipe_migrations.py +++ b/tests/integration_tests/recipe_migration_tests/test_recipe_migrations.py @@ -65,9 +65,11 @@ def test_recipe_migration(api_client: TestClient, unique_user: TestUser, mig: Mi response = api_client.get(api_routes.recipes, params=params, headers=unique_user.token) query_data = assert_derserialize(response) assert len(query_data["items"]) - slug = query_data["items"][0]["slug"] - response = api_client.get(api_routes.recipes_slug_timeline_events(slug), headers=unique_user.token) + recipe_id = query_data["items"][0]["id"] + params = {"queryFilter": f"recipe_id={recipe_id}"} + + response = api_client.get(api_routes.recipes_timeline_events, params=params, headers=unique_user.token) query_data = assert_derserialize(response) events = query_data["items"] assert len(events) diff --git a/tests/integration_tests/user_recipe_tests/test_recipe_crud.py b/tests/integration_tests/user_recipe_tests/test_recipe_crud.py index 7244d75cc60..305311ed89d 100644 --- a/tests/integration_tests/user_recipe_tests/test_recipe_crud.py +++ b/tests/integration_tests/user_recipe_tests/test_recipe_crud.py @@ -397,3 +397,30 @@ def test_delete_recipe_same_name(api_client: TestClient, unique_user: utils.Test response = api_client.get(api_routes.recipes_slug(slug), headers=unique_user.token) response = api_client.get(api_routes.recipes_slug(slug), headers=unique_user.token) assert response.status_code == 404 + + +def test_get_recipe_by_slug_or_id(api_client: TestClient, unique_user: utils.TestUser): + slugs = [random_string(10) for _ in range(3)] + + # Create recipes + for slug in slugs: + response = api_client.post(api_routes.recipes, json={"name": slug}, headers=unique_user.token) + assert response.status_code == 201 + assert json.loads(response.text) == slug + + # Get recipes by slug + recipe_ids = [] + for slug in slugs: + response = api_client.get(api_routes.recipes_slug(slug), headers=unique_user.token) + assert response.status_code == 200 + recipe_data = response.json() + assert recipe_data["slug"] == slug + recipe_ids.append(recipe_data["id"]) + + # Get recipes by id + for recipe_id, slug in zip(recipe_ids, slugs, strict=True): + response = api_client.get(api_routes.recipes_slug(recipe_id), headers=unique_user.token) + assert response.status_code == 200 + recipe_data = response.json() + assert recipe_data["slug"] == slug + assert recipe_data["id"] == recipe_id diff --git a/tests/integration_tests/user_recipe_tests/test_recipe_timeline_events.py b/tests/integration_tests/user_recipe_tests/test_recipe_timeline_events.py index 97832b1ec19..a50d7131f58 100644 --- a/tests/integration_tests/user_recipe_tests/test_recipe_timeline_events.py +++ b/tests/integration_tests/user_recipe_tests/test_recipe_timeline_events.py @@ -1,3 +1,5 @@ +from uuid import uuid4 + import pytest from fastapi.testclient import TestClient @@ -31,6 +33,7 @@ def recipes(api_client: TestClient, unique_user: TestUser): def test_create_timeline_event(api_client: TestClient, unique_user: TestUser, recipes: list[Recipe]): recipe = recipes[0] new_event = { + "recipe_id": str(recipe.id), "user_id": unique_user.user_id, "subject": random_string(), "event_type": "info", @@ -38,7 +41,7 @@ def test_create_timeline_event(api_client: TestClient, unique_user: TestUser, re } event_response = api_client.post( - api_routes.recipes_slug_timeline_events(recipe.slug), + api_routes.recipes_timeline_events, json=new_event, headers=unique_user.token, ) @@ -54,6 +57,7 @@ def test_get_all_timeline_events(api_client: TestClient, unique_user: TestUser, recipe = recipes[0] events_data = [ { + "recipe_id": str(recipe.id), "user_id": unique_user.user_id, "subject": random_string(), "event_type": "info", @@ -64,17 +68,16 @@ def test_get_all_timeline_events(api_client: TestClient, unique_user: TestUser, events: list[RecipeTimelineEventOut] = [] for event_data in events_data: + params: dict = {"queryFilter": f"recipe_id={event_data['recipe_id']}"} event_response = api_client.post( - api_routes.recipes_slug_timeline_events(recipe.slug), json=event_data, headers=unique_user.token + api_routes.recipes_timeline_events, params=params, json=event_data, headers=unique_user.token ) events.append(RecipeTimelineEventOut.parse_obj(event_response.json())) # check that we see them all params = {"page": 1, "perPage": -1} - events_response = api_client.get( - api_routes.recipes_slug_timeline_events(recipe.slug), params=params, headers=unique_user.token - ) + events_response = api_client.get(api_routes.recipes_timeline_events, params=params, headers=unique_user.token) events_pagination = RecipeTimelineEventPagination.parse_obj(events_response.json()) event_ids = [event.id for event in events] @@ -89,6 +92,7 @@ def test_get_timeline_event(api_client: TestClient, unique_user: TestUser, recip # create an event recipe = recipes[0] new_event_data = { + "recipe_id": str(recipe.id), "user_id": unique_user.user_id, "subject": random_string(), "event_type": "info", @@ -96,16 +100,14 @@ def test_get_timeline_event(api_client: TestClient, unique_user: TestUser, recip } event_response = api_client.post( - api_routes.recipes_slug_timeline_events(recipe.slug), + api_routes.recipes_timeline_events, json=new_event_data, headers=unique_user.token, ) new_event = RecipeTimelineEventOut.parse_obj(event_response.json()) # fetch the new event - event_response = api_client.get( - api_routes.recipes_slug_timeline_events_item_id(recipe.slug, new_event.id), headers=unique_user.token - ) + event_response = api_client.get(api_routes.recipes_timeline_events_item_id(new_event.id), headers=unique_user.token) assert event_response.status_code == 200 event = RecipeTimelineEventOut.parse_obj(event_response.json()) @@ -119,14 +121,13 @@ def test_update_timeline_event(api_client: TestClient, unique_user: TestUser, re # create an event recipe = recipes[0] new_event_data = { + "recipe_id": str(recipe.id), "user_id": unique_user.user_id, "subject": old_subject, "event_type": "info", } - event_response = api_client.post( - api_routes.recipes_slug_timeline_events(recipe.slug), json=new_event_data, headers=unique_user.token - ) + event_response = api_client.post(api_routes.recipes_timeline_events, json=new_event_data, headers=unique_user.token) new_event = RecipeTimelineEventOut.parse_obj(event_response.json()) assert new_event.subject == old_subject @@ -134,7 +135,7 @@ def test_update_timeline_event(api_client: TestClient, unique_user: TestUser, re updated_event_data = {"subject": new_subject} event_response = api_client.put( - api_routes.recipes_slug_timeline_events_item_id(recipe.slug, new_event.id), + api_routes.recipes_timeline_events_item_id(new_event.id), json=updated_event_data, headers=unique_user.token, ) @@ -149,20 +150,19 @@ def test_delete_timeline_event(api_client: TestClient, unique_user: TestUser, re # create an event recipe = recipes[0] new_event_data = { + "recipe_id": str(recipe.id), "user_id": unique_user.user_id, "subject": random_string(), "event_type": "info", "message": random_string(), } - event_response = api_client.post( - api_routes.recipes_slug_timeline_events(recipe.slug), json=new_event_data, headers=unique_user.token - ) + event_response = api_client.post(api_routes.recipes_timeline_events, json=new_event_data, headers=unique_user.token) new_event = RecipeTimelineEventOut.parse_obj(event_response.json()) # delete the event event_response = api_client.delete( - api_routes.recipes_slug_timeline_events_item_id(recipe.slug, new_event.id), headers=unique_user.token + api_routes.recipes_timeline_events_item_id(new_event.id), headers=unique_user.token ) assert event_response.status_code == 200 @@ -171,7 +171,7 @@ def test_delete_timeline_event(api_client: TestClient, unique_user: TestUser, re # try to get the event event_response = api_client.get( - api_routes.recipes_slug_timeline_events_item_id(recipe.slug, deleted_event.id), headers=unique_user.token + api_routes.recipes_timeline_events_item_id(deleted_event.id), headers=unique_user.token ) assert event_response.status_code == 404 @@ -180,6 +180,7 @@ def test_timeline_event_message_alias(api_client: TestClient, unique_user: TestU # create an event using aliases recipe = recipes[0] new_event_data = { + "recipeId": str(recipe.id), "userId": unique_user.user_id, "subject": random_string(), "eventType": "info", @@ -187,7 +188,7 @@ def test_timeline_event_message_alias(api_client: TestClient, unique_user: TestU } event_response = api_client.post( - api_routes.recipes_slug_timeline_events(recipe.slug), + api_routes.recipes_timeline_events, json=new_event_data, headers=unique_user.token, ) @@ -197,9 +198,7 @@ def test_timeline_event_message_alias(api_client: TestClient, unique_user: TestU assert new_event.message == new_event_data["eventMessage"] # fetch the new event - event_response = api_client.get( - api_routes.recipes_slug_timeline_events_item_id(recipe.slug, new_event.id), headers=unique_user.token - ) + event_response = api_client.get(api_routes.recipes_timeline_events_item_id(new_event.id), headers=unique_user.token) assert event_response.status_code == 200 event = RecipeTimelineEventOut.parse_obj(event_response.json()) @@ -211,7 +210,7 @@ def test_timeline_event_message_alias(api_client: TestClient, unique_user: TestU updated_event_data = {"subject": new_subject, "eventMessage": new_message} event_response = api_client.put( - api_routes.recipes_slug_timeline_events_item_id(recipe.slug, new_event.id), + api_routes.recipes_timeline_events_item_id(new_event.id), json=updated_event_data, headers=unique_user.token, ) @@ -225,71 +224,20 @@ def test_timeline_event_message_alias(api_client: TestClient, unique_user: TestU def test_create_recipe_with_timeline_event(api_client: TestClient, unique_user: TestUser, recipes: list[Recipe]): # make sure when the recipes fixture was created that all recipes have at least one event for recipe in recipes: - events_response = api_client.get( - api_routes.recipes_slug_timeline_events(recipe.slug), headers=unique_user.token - ) + params = {"queryFilter": f"recipe_id={recipe.id}"} + events_response = api_client.get(api_routes.recipes_timeline_events, params=params, headers=unique_user.token) events_pagination = RecipeTimelineEventPagination.parse_obj(events_response.json()) assert events_pagination.items -def test_invalid_recipe_slug(api_client: TestClient, unique_user: TestUser): +def test_invalid_recipe_id(api_client: TestClient, unique_user: TestUser): new_event_data = { + "recipe_id": str(uuid4()), "user_id": unique_user.user_id, "subject": random_string(), "event_type": "info", "message": random_string(), } - event_response = api_client.post( - api_routes.recipes_slug_timeline_events(random_string()), json=new_event_data, headers=unique_user.token - ) + event_response = api_client.post(api_routes.recipes_timeline_events, json=new_event_data, headers=unique_user.token) assert event_response.status_code == 404 - - -def test_recipe_slug_mismatch(api_client: TestClient, unique_user: TestUser, recipes: list[Recipe]): - # get new recipes - recipe = recipes[0] - invalid_recipe = recipes[1] - - # create a new event - new_event_data = { - "user_id": unique_user.user_id, - "subject": random_string(), - "event_type": "info", - "message": random_string(), - } - - event_response = api_client.post( - api_routes.recipes_slug_timeline_events(recipe.slug), json=new_event_data, headers=unique_user.token - ) - event = RecipeTimelineEventOut.parse_obj(event_response.json()) - - # try to perform operations on the event using the wrong recipe - event_response = api_client.get( - api_routes.recipes_slug_timeline_events_item_id(invalid_recipe.slug, event.id), - headers=unique_user.token, - ) - assert event_response.status_code == 404 - - event_response = api_client.put( - api_routes.recipes_slug_timeline_events_item_id(invalid_recipe.slug, event.id), - json=new_event_data, - headers=unique_user.token, - ) - assert event_response.status_code == 404 - - event_response = api_client.delete( - api_routes.recipes_slug_timeline_events_item_id(invalid_recipe.slug, event.id), - headers=unique_user.token, - ) - assert event_response.status_code == 404 - - # make sure the event still exists and is unmodified - event_response = api_client.get( - api_routes.recipes_slug_timeline_events_item_id(recipe.slug, event.id), - headers=unique_user.token, - ) - assert event_response.status_code == 200 - - existing_event = RecipeTimelineEventOut.parse_obj(event_response.json()) - assert existing_event == event diff --git a/tests/unit_tests/repository_tests/test_pagination.py b/tests/unit_tests/repository_tests/test_pagination.py index f1c4adf0cc0..322b1bf8837 100644 --- a/tests/unit_tests/repository_tests/test_pagination.py +++ b/tests/unit_tests/repository_tests/test_pagination.py @@ -1,4 +1,5 @@ import time +from collections import defaultdict from random import randint from urllib.parse import parse_qsl, urlsplit @@ -11,13 +12,16 @@ from mealie.schema.recipe.recipe_ingredient import IngredientUnit, SaveIngredientUnit from mealie.schema.response.pagination import PaginationQuery from mealie.services.seeder.seeder_service import SeederService +from tests.utils import api_routes +from tests.utils.factories import random_int, random_string from tests.utils.fixture_schemas import TestUser def test_repository_pagination(database: AllRepositories, unique_user: TestUser): group = database.groups.get_one(unique_user.group_id) + assert group - seeder = SeederService(database, None, group) + seeder = SeederService(database, None, group) # type: ignore seeder.seed_foods("en-US") foods_repo = database.ingredient_foods.by_group(unique_user.group_id) # type: ignore @@ -50,8 +54,9 @@ def test_repository_pagination(database: AllRepositories, unique_user: TestUser) def test_pagination_response_and_metadata(database: AllRepositories, unique_user: TestUser): group = database.groups.get_one(unique_user.group_id) + assert group - seeder = SeederService(database, None, group) + seeder = SeederService(database, None, group) # type: ignore seeder.seed_foods("en-US") foods_repo = database.ingredient_foods.by_group(unique_user.group_id) # type: ignore @@ -78,8 +83,9 @@ def test_pagination_response_and_metadata(database: AllRepositories, unique_user def test_pagination_guides(database: AllRepositories, unique_user: TestUser): group = database.groups.get_one(unique_user.group_id) + assert group - seeder = SeederService(database, None, group) + seeder = SeederService(database, None, group) # type: ignore seeder.seed_foods("en-US") foods_repo = database.ingredient_foods.by_group(unique_user.group_id) # type: ignore @@ -107,10 +113,10 @@ def test_pagination_guides(database: AllRepositories, unique_user: TestUser): random_page_of_results = foods_repo.page_all(query) random_page_of_results.set_pagination_guides(foods_route, query.dict()) - next_params = dict(parse_qsl(urlsplit(random_page_of_results.next).query)) + next_params: dict = dict(parse_qsl(urlsplit(random_page_of_results.next).query)) # type: ignore assert int(next_params["page"]) == random_page + 1 - prev_params = dict(parse_qsl(urlsplit(random_page_of_results.previous).query)) + prev_params: dict = dict(parse_qsl(urlsplit(random_page_of_results.previous).query)) # type: ignore assert int(prev_params["page"]) == random_page - 1 source_params = camelize(query.dict()) @@ -173,7 +179,7 @@ def test_pagination_filter_datetimes( unit_1 = query_units[1] unit_2 = query_units[2] - dt = unit_2.created_at.isoformat() + dt = unit_2.created_at.isoformat() # type: ignore query = PaginationQuery(page=1, per_page=-1, query_filter=f'createdAt>="{dt}"') unit_results = units_repo.page_all(query).items assert len(unit_results) == 2 @@ -194,7 +200,7 @@ def test_pagination_filter_advanced(query_units: tuple[RepositoryUnit, Ingredien units_repo = query_units[0] unit_3 = query_units[3] - dt = unit_3.created_at.isoformat() + dt = str(unit_3.created_at.isoformat()) # type: ignore qf = f'name="test unit 1" OR (useAbbreviation=f AND (name="test unit 2" OR createdAt > "{dt}"))' query = PaginationQuery(page=1, per_page=-1, query_filter=qf) unit_results = units_repo.page_all(query).items @@ -206,8 +212,11 @@ def test_pagination_filter_advanced(query_units: tuple[RepositoryUnit, Ingredien "qf", [ pytest.param('(name="test name" AND useAbbreviation=f))', id="unbalanced parenthesis"), + pytest.param('id="this is not a valid UUID"', id="invalid UUID"), pytest.param('createdAt="this is not a valid datetime format"', id="invalid datetime format"), pytest.param('badAttribute="test value"', id="invalid attribute"), + pytest.param('group.badAttribute="test value"', id="bad nested attribute"), + pytest.param('group.preferences.badAttribute="test value"', id="bad double nested attribute"), ], ) def test_malformed_query_filters(api_client: TestClient, unique_user: TestUser, qf: str): @@ -216,3 +225,46 @@ def test_malformed_query_filters(api_client: TestClient, unique_user: TestUser, response = api_client.get(route, params={"queryFilter": qf}, headers=unique_user.token) assert response.status_code == 400 + + +def test_pagination_filter_nested(api_client: TestClient, user_tuple: list[TestUser]): + # create a few recipes for each user + slugs: defaultdict[int, list[str]] = defaultdict(list) + for i, user in enumerate(user_tuple): + for _ in range(random_int(3, 5)): + slug: str = random_string() + response = api_client.post(api_routes.recipes, json={"name": slug}, headers=user.token) + + assert response.status_code == 201 + slugs[i].append(slug) + + # query recipes with a nested user filter + recipe_ids: defaultdict[int, list[str]] = defaultdict(list) + for i, user in enumerate(user_tuple): + params = {"page": 1, "perPage": -1, "queryFilter": f'user.id="{user.user_id}"'} + response = api_client.get(api_routes.recipes, params=params, headers=user.token) + + assert response.status_code == 200 + recipes_data: list[dict] = response.json()["items"] + assert recipes_data + + for recipe_data in recipes_data: + slug = recipe_data["slug"] + assert slug in slugs[i] + assert slug not in slugs[(i + 1) % len(user_tuple)] + + recipe_ids[i].append(recipe_data["id"]) + + # query timeline events with a double nested recipe.user filter + for i, user in enumerate(user_tuple): + params = {"page": 1, "perPage": -1, "queryFilter": f'recipe.user.id="{user.user_id}"'} + response = api_client.get(api_routes.recipes_timeline_events, params=params, headers=user.token) + + assert response.status_code == 200 + events_data: list[dict] = response.json()["items"] + assert events_data + + for event_data in events_data: + recipe_id = event_data["recipeId"] + assert recipe_id in recipe_ids[i] + assert recipe_id not in recipe_ids[(i + 1) % len(user_tuple)] diff --git a/tests/unit_tests/services_tests/scheduler/tasks/test_create_timeline_events.py b/tests/unit_tests/services_tests/scheduler/tasks/test_create_timeline_events.py index 08bf47b989a..f87b2af4d5a 100644 --- a/tests/unit_tests/services_tests/scheduler/tasks/test_create_timeline_events.py +++ b/tests/unit_tests/services_tests/scheduler/tasks/test_create_timeline_events.py @@ -5,9 +5,7 @@ from mealie.schema.meal_plan.new_meal import CreatePlanEntry from mealie.schema.recipe.recipe import RecipeSummary -from mealie.services.scheduler.tasks.create_timeline_events import ( - create_mealplan_timeline_events, -) +from mealie.services.scheduler.tasks.create_timeline_events import create_mealplan_timeline_events from tests import utils from tests.utils import api_routes from tests.utils.factories import random_int, random_string @@ -31,7 +29,8 @@ def test_new_mealplan_event(api_client: TestClient, unique_user: TestUser): assert recipe.last_made is None # store the number of events, so we can compare later - response = api_client.get(api_routes.recipes_slug_timeline_events(recipe_name), headers=unique_user.token) + params = {"queryFilter": f"recipe_id={recipe_id}"} + response = api_client.get(api_routes.recipes_timeline_events, params=params, headers=unique_user.token) response_json = response.json() initial_event_count = len(response_json["items"]) @@ -45,10 +44,14 @@ def test_new_mealplan_event(api_client: TestClient, unique_user: TestUser): # run the task and check to make sure a new event was created from the mealplan create_mealplan_timeline_events() - params = {"page": "1", "perPage": "-1", "orderBy": "created_at", "orderDirection": "desc"} - response = api_client.get( - api_routes.recipes_slug_timeline_events(recipe_name), headers=unique_user.token, params=params - ) + params = { + "page": "1", + "perPage": "-1", + "orderBy": "created_at", + "orderDirection": "desc", + "queryFilter": f"recipe_id={recipe_id}", + } + response = api_client.get(api_routes.recipes_timeline_events, headers=unique_user.token, params=params) response_json = response.json() assert len(response_json["items"]) == initial_event_count + 1 @@ -91,7 +94,8 @@ def test_new_mealplan_event_duplicates(api_client: TestClient, unique_user: Test recipe_id = recipe.id # store the number of events, so we can compare later - response = api_client.get(api_routes.recipes_slug_timeline_events(recipe_name), headers=unique_user.token) + params = {"queryFilter": f"recipe_id={recipe_id}"} + response = api_client.get(api_routes.recipes_timeline_events, params=params, headers=unique_user.token) response_json = response.json() initial_event_count = len(response_json["items"]) @@ -106,10 +110,14 @@ def test_new_mealplan_event_duplicates(api_client: TestClient, unique_user: Test for _ in range(3): create_mealplan_timeline_events() - params = {"page": "1", "perPage": "-1", "orderBy": "created_at", "orderDirection": "desc"} - response = api_client.get( - api_routes.recipes_slug_timeline_events(recipe_name), headers=unique_user.token, params=params - ) + params = { + "page": "1", + "perPage": "-1", + "orderBy": "created_at", + "orderDirection": "desc", + "queryFilter": f"recipe_id={recipe_id}", + } + response = api_client.get(api_routes.recipes_timeline_events, headers=unique_user.token, params=params) response_json = response.json() assert len(response_json["items"]) == initial_event_count + 1 @@ -125,7 +133,8 @@ def test_new_mealplan_events_with_multiple_recipes(api_client: TestClient, uniqu recipes.append(RecipeSummary.parse_obj(response.json())) # store the number of events, so we can compare later - response = api_client.get(api_routes.recipes_slug_timeline_events(str(recipes[0].slug)), headers=unique_user.token) + params = {"queryFilter": f"recipe_id={recipes[0].id}"} + response = api_client.get(api_routes.recipes_timeline_events, params=params, headers=unique_user.token) response_json = response.json() initial_event_count = len(response_json["items"]) @@ -149,10 +158,14 @@ def test_new_mealplan_events_with_multiple_recipes(api_client: TestClient, uniqu for recipe in recipes: target_count = initial_event_count + mealplan_count_by_recipe_id[recipe.id] # type: ignore - params = {"page": "1", "perPage": "-1", "orderBy": "created_at", "orderDirection": "desc"} - response = api_client.get( - api_routes.recipes_slug_timeline_events(recipe.slug), headers=unique_user.token, params=params - ) + params = { + "page": "1", + "perPage": "-1", + "orderBy": "created_at", + "orderDirection": "desc", + "queryFilter": f"recipe_id={recipe.id}", + } + response = api_client.get(api_routes.recipes_timeline_events, headers=unique_user.token, params=params) response_json = response.json() assert len(response_json["items"]) == target_count @@ -167,10 +180,9 @@ def test_new_mealplan_events_with_multiple_recipes(api_client: TestClient, uniqu "perPage": "-1", "orderBy": "created_at", "orderDirection": "desc", + "queryFilter": f"recipe_id={recipe.id}", } - response = api_client.get( - api_routes.recipes_slug_timeline_events(recipe.slug), headers=unique_user.token, params=params - ) + response = api_client.get(api_routes.recipes_timeline_events, headers=unique_user.token, params=params) response_json = response.json() assert len(response_json["items"]) == target_count diff --git a/tests/utils/api_routes/__init__.py b/tests/utils/api_routes/__init__.py index 44fef50ba26..fbc46c9b619 100644 --- a/tests/utils/api_routes/__init__.py +++ b/tests/utils/api_routes/__init__.py @@ -39,6 +39,8 @@ """`/api/admin/server-tasks`""" admin_users = "/api/admin/users" """`/api/admin/users`""" +admin_users_password_reset_token = "/api/admin/users/password-reset-token" +"""`/api/admin/users/password-reset-token`""" admin_users_unlock = "/api/admin/users/unlock" """`/api/admin/users/unlock`""" app_about = "/api/app/about" @@ -159,6 +161,8 @@ """`/api/recipes/summary/untagged`""" recipes_test_scrape_url = "/api/recipes/test-scrape-url" """`/api/recipes/test-scrape-url`""" +recipes_timeline_events = "/api/recipes/timeline/events" +"""`/api/recipes/timeline/events`""" shared_recipes = "/api/shared/recipes" """`/api/shared/recipes`""" units = "/api/units" @@ -386,14 +390,9 @@ def recipes_slug_last_made(slug): return f"{prefix}/recipes/{slug}/last-made" -def recipes_slug_timeline_events(slug): - """`/api/recipes/{slug}/timeline/events`""" - return f"{prefix}/recipes/{slug}/timeline/events" - - -def recipes_slug_timeline_events_item_id(slug, item_id): - """`/api/recipes/{slug}/timeline/events/{item_id}`""" - return f"{prefix}/recipes/{slug}/timeline/events/{item_id}" +def recipes_timeline_events_item_id(item_id): + """`/api/recipes/timeline/events/{item_id}`""" + return f"{prefix}/recipes/timeline/events/{item_id}" def shared_recipes_item_id(item_id): From 75698c531aaa057e1d35ae5bd4fb09ee8a11b47b Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Tue, 25 Apr 2023 12:46:58 -0500 Subject: [PATCH 3/3] fix: Shopping List Label Dropdown Doesn't Save Correctly (#2361) * only update items by label on refresh * made changes more responsive * fast re-order items when labels are re-ordered --- .../Domain/ShoppingList/ShoppingListItem.vue | 4 ++ frontend/pages/shopping-lists/_id.vue | 39 ++++++++++++------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/frontend/components/Domain/ShoppingList/ShoppingListItem.vue b/frontend/components/Domain/ShoppingList/ShoppingListItem.vue index 93b2604ed44..0cb6e8dd3c6 100644 --- a/frontend/components/Domain/ShoppingList/ShoppingListItem.vue +++ b/frontend/components/Domain/ShoppingList/ShoppingListItem.vue @@ -138,6 +138,10 @@ export default defineComponent({ }); const edit = ref(false); function toggleEdit(val = !edit.value) { + if (edit.value === val) { + return; + } + if (val) { // update local copy of item with the current value localListItem.value = props.value; diff --git a/frontend/pages/shopping-lists/_id.vue b/frontend/pages/shopping-lists/_id.vue index 9b87fd006dc..f80f1ff1a9d 100644 --- a/frontend/pages/shopping-lists/_id.vue +++ b/frontend/pages/shopping-lists/_id.vue @@ -32,7 +32,7 @@
- + {{ $globals.icons.tags }} @@ -206,14 +206,13 @@