diff --git a/.changeset/chilled-walls-shop.md b/.changeset/chilled-walls-shop.md index e250315fce..3e0182fc81 100644 --- a/.changeset/chilled-walls-shop.md +++ b/.changeset/chilled-walls-shop.md @@ -8,7 +8,7 @@ Add the rule `@typescript-eslint/prefer-enum-initializers` to require enum initi // ✅ enum ExampleEnum { One = "One", - Two = "Two" + Two = "Two", } ``` @@ -16,6 +16,6 @@ enum ExampleEnum { // ❌ enum ExampleEnum { One, - Two + Two, } -``` \ No newline at end of file +``` diff --git a/.changeset/curly-pillows-decide.md b/.changeset/curly-pillows-decide.md index 0f06a720e9..58276674a4 100644 --- a/.changeset/curly-pillows-decide.md +++ b/.changeset/curly-pillows-decide.md @@ -13,11 +13,7 @@ You can use it like this: ( - - {/* ... */} - - ), + Toolbar: () => {/* ... */}, }} /> -``` \ No newline at end of file +``` diff --git a/.changeset/curvy-moles-punch.md b/.changeset/curvy-moles-punch.md index 629431f568..aae724d896 100644 --- a/.changeset/curvy-moles-punch.md +++ b/.changeset/curvy-moles-punch.md @@ -4,10 +4,10 @@ Rework `typographyOptions` -- Replace `typographyOptions` with `createTypographyOptions()` to enable using the theme's breakpoints for media queries -- Add new default styles for variants `subtitle1`, `subtitle2`, `caption` and `overline` -- Remove custom `fontWeights` -- Switch the font from `Roboto` to `Roboto Flex` +- Replace `typographyOptions` with `createTypographyOptions()` to enable using the theme's breakpoints for media queries +- Add new default styles for variants `subtitle1`, `subtitle2`, `caption` and `overline` +- Remove custom `fontWeights` +- Switch the font from `Roboto` to `Roboto Flex` The font switch requires you to make the following two changes in your admin application: @@ -19,11 +19,11 @@ The font switch requires you to make the following two changes in your admin app + "@fontsource-variable/roboto-flex": "^5.0.0", ``` -```diff +```diff // App.tsx - import "@fontsource/roboto/100.css"; - import "@fontsource/roboto/300.css"; - import "@fontsource/roboto/400.css"; - import "@fontsource/roboto/500.css"; + import "@fontsource-variable/roboto-flex/full.css"; -``` \ No newline at end of file +``` diff --git a/.changeset/eighty-owls-cheat.md b/.changeset/eighty-owls-cheat.md index 6b55fd388d..acc051b683 100644 --- a/.changeset/eighty-owls-cheat.md +++ b/.changeset/eighty-owls-cheat.md @@ -4,12 +4,12 @@ Rework colors -- Rename `bluePalette` to `primaryPalette` -- Rename `neutrals` to `greyPalette` -- Remove `greenPalette` -- Change colors in all palettes -- Change `text` colors -- Add `highlight` colors `purple`, `green`, `orange`, `yellow` and `red` to palette +- Rename `bluePalette` to `primaryPalette` +- Rename `neutrals` to `greyPalette` +- Remove `greenPalette` +- Change colors in all palettes +- Change `text` colors +- Add `highlight` colors `purple`, `green`, `orange`, `yellow` and `red` to palette Hint: To use the `highlight` colors without getting a type error, you must adjust the `vendors.d.ts` in your project: diff --git a/.changeset/fast-dodos-compete.md b/.changeset/fast-dodos-compete.md index 6f24fbf510..723c5d84ff 100644 --- a/.changeset/fast-dodos-compete.md +++ b/.changeset/fast-dodos-compete.md @@ -4,4 +4,4 @@ Add `ForcePromptRoute`, a `Route` that triggers a prompt even if it is a subroute -Used in `StackSwitch` so that navigating to a nested stack subpage will show a prompt (if dirty) \ No newline at end of file +Used in `StackSwitch` so that navigating to a nested stack subpage will show a prompt (if dirty) diff --git a/.changeset/fifty-keys-sit.md b/.changeset/fifty-keys-sit.md index 9ab47c1456..78a41b4809 100644 --- a/.changeset/fifty-keys-sit.md +++ b/.changeset/fifty-keys-sit.md @@ -10,7 +10,7 @@ The new `ContentScopeIndicator` has the logic for displaying the current scope b Usage: -- Per default, the `ContentScopeIndicator` displays the current `ContentScope` -- Pass a scope object via the `scope` prop if your page has a custom scope -- Pass the `global` prop if your page has no scope -- Pass `children` if you want to render completely custom content \ No newline at end of file +- Per default, the `ContentScopeIndicator` displays the current `ContentScope` +- Pass a scope object via the `scope` prop if your page has a custom scope +- Pass the `global` prop if your page has no scope +- Pass `children` if you want to render completely custom content diff --git a/.changeset/fifty-toes-wink.md b/.changeset/fifty-toes-wink.md new file mode 100644 index 0000000000..488028f434 --- /dev/null +++ b/.changeset/fifty-toes-wink.md @@ -0,0 +1,7 @@ +--- +"@comet/admin": patch +--- + +Fix error dialog to show GraphQL errors again + +Previously, GraphQL errors without an http status code didn't trigger an error dialog anymore. diff --git a/.changeset/friendly-mayflies-play.md b/.changeset/friendly-mayflies-play.md index c4ee8106fc..726bf0f6c4 100644 --- a/.changeset/friendly-mayflies-play.md +++ b/.changeset/friendly-mayflies-play.md @@ -10,31 +10,31 @@ See the [docs](https://nextjs.org/docs/pages/api-reference/components/image-lega Remove the `layout` prop from the block as it can lead to errors with the default implementation (`layout="responsive"` is not compatible with the new `fill` prop). -- `layout={"responsive" | "inherit"}` can safely be removed +- `layout={"responsive" | "inherit"}` can safely be removed ```diff - ``` -- `layout={"fill"}` can be replaced with `fill={true}` +- `layout={"fill"}` can be replaced with `fill={true}` ```diff - ``` -Notes: +Notes: The `PixelImageBlock` is usually wrapped in a `DamImageBlock` in the application. The `layout` prop should be removed from it as well. -You can use the newly added `fill` prop of the `next/image` component by embedding the `PixelImageBlock` in a parent element that assigns the `position` style. See the [docs](https://nextjs.org/docs/pages/api-reference/components/image#fill) for more information. +You can use the newly added `fill` prop of the `next/image` component by embedding the `PixelImageBlock` in a parent element that assigns the `position` style. See the [docs](https://nextjs.org/docs/pages/api-reference/components/image#fill) for more information. diff --git a/.changeset/fuzzy-foxes-laugh.md b/.changeset/fuzzy-foxes-laugh.md index 0ae77afc82..235fcd3c4e 100644 --- a/.changeset/fuzzy-foxes-laugh.md +++ b/.changeset/fuzzy-foxes-laugh.md @@ -4,5 +4,5 @@ Make icon required for top level menu and group items -This fixes the problem, that there was no icon or text to display in the collapsed state of the menu if no icon was passed. -Icons are required for all top level menu items and the items of groups. Groups themselves do not require an icon. \ No newline at end of file +This fixes the problem, that there was no icon or text to display in the collapsed state of the menu if no icon was passed. +Icons are required for all top level menu items and the items of groups. Groups themselves do not require an icon. diff --git a/.changeset/gentle-chefs-sell.md b/.changeset/gentle-chefs-sell.md index 0878d53118..27274fe9f5 100644 --- a/.changeset/gentle-chefs-sell.md +++ b/.changeset/gentle-chefs-sell.md @@ -4,4 +4,4 @@ Remove the `requiredSymbol` prop from `FieldContainer` and use MUIs native implementation -This prop was used to display a custom required symbol next to the label of the field. We now use the native implementation of the required attribute of MUI to ensure better accessibility and compatibility with screen readers. +This prop was used to display a custom required symbol next to the label of the field. We now use the native implementation of the required attribute of MUI to ensure better accessibility and compatibility with screen readers. diff --git a/.changeset/giant-apples-cheer.md b/.changeset/giant-apples-cheer.md index 55e1498395..c876eb06e7 100644 --- a/.changeset/giant-apples-cheer.md +++ b/.changeset/giant-apples-cheer.md @@ -4,5 +4,5 @@ API Generator: Add new `dedicatedResolverArg` option to `@CrudField` to generate better API for Many-to-one-relations -- Add foreign id as argument to create mutation -- Add foreign id as argument to list query +- Add foreign id as argument to create mutation +- Add foreign id as argument to list query diff --git a/.changeset/giant-ladybugs-greet.md b/.changeset/giant-ladybugs-greet.md index 440cb21114..6df683edf1 100644 --- a/.changeset/giant-ladybugs-greet.md +++ b/.changeset/giant-ladybugs-greet.md @@ -15,4 +15,3 @@ Rework `Toolbar` It automatically switches from a normal `Button` to an `IconButton` for smaller screen sizes. - To show a scope indicator, you must pass a `` to the `Toolbar` via the `scopeIndicator` prop - diff --git a/.changeset/khaki-planets-matter.md b/.changeset/khaki-planets-matter.md new file mode 100644 index 0000000000..e46ae0aac9 --- /dev/null +++ b/.changeset/khaki-planets-matter.md @@ -0,0 +1,20 @@ +--- +"@comet/cms-admin": minor +"@comet/cms-api": minor +--- + +Require a file extension when changing the filename in the DAM + +Previously, files in the DAM could be renamed without restrictions. +Files could have invalid extensions (for their mimetype) or no extension at all. +This theoretically made the following attack possible: + +1. Creating a dangerous .exe file locally +2. Renaming it to .jpg locally +3. Uploading the file as a .jpg +4. Renaming it to .exe in the DAM +5. The file is now downloaded as .exe + +Now, filenames must always have an extension that matches their mimetype. +This is enforced in the admin and API. +Existing files without an extension are automatically assigned an extension via a DB migration. diff --git a/.changeset/long-crabs-bake.md b/.changeset/long-crabs-bake.md index ae00ec0ebf..2414467679 100644 --- a/.changeset/long-crabs-bake.md +++ b/.changeset/long-crabs-bake.md @@ -4,5 +4,5 @@ Remove the `components` and `componentProps` props from `CopyToClipboardButton` -Instead, for the icons, use the `copyIcon` and `successIcon` props to pass a `ReactNode` instead of separately passing in values to the `components` and `componentProps` objects. -Use `slotPops` to pass props to the remaining elements. \ No newline at end of file +Instead, for the icons, use the `copyIcon` and `successIcon` props to pass a `ReactNode` instead of separately passing in values to the `components` and `componentProps` objects. +Use `slotPops` to pass props to the remaining elements. diff --git a/.changeset/nice-emus-confess.md b/.changeset/nice-emus-confess.md new file mode 100644 index 0000000000..a736800764 --- /dev/null +++ b/.changeset/nice-emus-confess.md @@ -0,0 +1,11 @@ +--- +"@comet/cms-api": minor +--- + +Loosen the filename slugification rules + +When uploading a file to the DAM, the filename is automatically slugified. +Previously, the slugification used pretty strict rules without a good reason. + +Now, the rules were loosened allowing uppercase characters and most special characters. +Also, slugify now uses the locale `en` instead of `de` for special character replacements. diff --git a/.changeset/ninety-countries-chew.md b/.changeset/ninety-countries-chew.md index 3993e4591a..a05249ad40 100644 --- a/.changeset/ninety-countries-chew.md +++ b/.changeset/ninety-countries-chew.md @@ -2,4 +2,4 @@ "@comet/cms-site": minor --- -Add optional `fill` prop to `YouTubeVideoBlock` and `DamVideoBlock` to support same behavior as in `PixelImageBlock` +Add optional `fill` prop to `YouTubeVideoBlock` and `DamVideoBlock` to support same behavior as in `PixelImageBlock` diff --git a/.changeset/plenty-cougars-warn.md b/.changeset/plenty-cougars-warn.md index a664a60ab6..d0ee9aac64 100644 --- a/.changeset/plenty-cougars-warn.md +++ b/.changeset/plenty-cougars-warn.md @@ -4,13 +4,14 @@ Add GraphQL fetch client -- `createGraphQLFetch`: simple graphql client around fetch, usage: createGraphQLFetch(fetch, url)(gql, variables) -- `type GraphQLFetch = (query: string, variables?: V, init?: RequestInit) => Promise` -- `gql` for tagging queries -- `createFetchWithDefaults` fetch decorator that adds default values (eg. headers or next.revalidate) -- `createFetchWithPreviewHeaders` fetch decorator that adds comet preview headers (based on SitePreviewData) +- `createGraphQLFetch`: simple graphql client around fetch, usage: createGraphQLFetch(fetch, url)(gql, variables) +- `type GraphQLFetch = (query: string, variables?: V, init?: RequestInit) => Promise` +- `gql` for tagging queries +- `createFetchWithDefaults` fetch decorator that adds default values (eg. headers or next.revalidate) +- `createFetchWithPreviewHeaders` fetch decorator that adds comet preview headers (based on SitePreviewData) Example helper in application: + ``` export const graphQLApiUrl = `${typeof window === "undefined" ? process.env.API_URL_INTERNAL : process.env.NEXT_PUBLIC_API_URL}/graphql`; export function createGraphQLFetch(previewData?: SitePreviewData) { @@ -23,6 +24,7 @@ export function createGraphQLFetch(previewData?: SitePreviewData) { ``` Usage example: + ``` const graphqlFetch = createGraphQLFetch(previewData); const data = await graphqlFetch( @@ -31,4 +33,4 @@ const data = await graphqlFetch( exampleVariable: "foo" } ); -``` \ No newline at end of file +``` diff --git a/.changeset/popular-planes-tap.md b/.changeset/popular-planes-tap.md index fb6094f517..d89dc5a532 100644 --- a/.changeset/popular-planes-tap.md +++ b/.changeset/popular-planes-tap.md @@ -18,4 +18,3 @@ Move `YouTubeVideoBlock` to `@cms` packages - import { YouTubeVideoBlock } from "@comet/blocks-api"; + import { YouTubeVideoBlock } from "@comet/cms-api"; ``` - diff --git a/.changeset/pre.json b/.changeset/pre.json index bb6a3ad079..5f8f460a83 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,150 +1,150 @@ { - "mode": "pre", - "tag": "beta", - "initialVersions": { - "comet-demo-admin": "1.0.0", - "comet-demo-admin-server": "1.0.0", - "comet-demo-api": "1.0.0", - "comet-demo-site": "1.0.0", - "comet-demo-site-pages": "1.0.0", - "comet-docs": "0.0.0", - "@comet/admin": "6.10.0", - "@comet/admin-babel-preset": "6.10.0", - "@comet/admin-color-picker": "6.10.0", - "@comet/admin-date-time": "6.10.0", - "@comet/admin-icons": "6.10.0", - "@comet/admin-react-select": "6.10.0", - "@comet/admin-rte": "6.10.0", - "@comet/admin-theme": "6.10.0", - "@comet/blocks-admin": "6.10.0", - "@comet/cms-admin": "6.10.0", - "@comet/blocks-api": "6.10.0", - "@comet/cms-api": "6.10.0", - "@comet/cli": "6.10.0", - "@comet/eslint-config": "6.10.0", - "@comet/eslint-plugin": "6.10.0", - "@comet/cms-site": "6.10.0", - "comet-storybook": "1.0.0" - }, - "changesets": [ - "blue-apricots-appear", - "brave-kiwis-pay", - "bright-dolls-live", - "brown-kids-develop", - "chatty-walls-study", - "chilled-walls-shop", - "clean-apes-vanish", - "clean-insects-happen", - "clever-clocks-smile", - "cool-kangaroos-cough", - "cool-kiwis-shop", - "curly-pillows-decide", - "curvy-moles-punch", - "cyan-drinks-enjoy", - "cyan-ladybugs-bathe", - "dirty-rabbits-tease", - "early-news-attend", - "eight-rules-laugh", - "eighty-owls-cheat", - "empty-islands-confess", - "fair-waves-breathe", - "fast-dodos-compete", - "fast-pens-laugh", - "fifty-keys-sit", - "fifty-suits-drive", - "fluffy-oranges-approve", - "fluffy-sheep-hammer", - "forty-bees-exercise", - "forty-hounds-work", - "fresh-buckets-begin", - "fresh-sheep-remember", - "friendly-mayflies-play", - "funny-paws-dream", - "fuzzy-foxes-laugh", - "gentle-chefs-sell", - "gentle-pots-perform", - "giant-apples-cheer", - "giant-humans-lick", - "giant-ladybugs-greet", - "giant-suns-begin", - "good-squids-join", - "great-hotels-unite", - "great-insects-reply", - "grumpy-poets-own", - "happy-fans-pump", - "happy-ladybugs-smile", - "hip-garlics-press", - "hot-buckets-talk", - "hot-carpets-eat", - "hot-drinks-crash", - "khaki-cobras-trade", - "khaki-eels-try", - "large-mirrors-teach", - "light-chefs-lie", - "light-ligers-wonder", - "long-bottles-brush", - "long-crabs-bake", - "long-items-sell", - "many-rivers-exist", - "mean-coats-matter", - "metal-bugs-beg", - "metal-seas-listen", - "modern-glasses-sort", - "nervous-sloths-thank", - "nervous-windows-grin", - "new-chicken-search", - "ninety-adults-peel", - "plenty-cougars-warn", - "polite-ducks-juggle", - "polite-needles-unite", - "poor-dragons-sing", - "popular-ducks-vanish", - "popular-planes-tap", - "popular-shirts-pretend", - "pretty-deers-trade", - "pretty-kangaroos-repair", - "proud-cheetahs-sleep", - "quick-foxes-notice", - "rare-boxes-guess", - "rare-crabs-burn", - "real-roses-applaud", - "rotten-clocks-shop", - "selfish-books-fail", - "selfish-dolls-beg", - "selfish-shrimps-explode", - "serious-bats-boil", - "serious-ravens-matter", - "sharp-zebras-hear", - "short-donkeys-double", - "shy-jars-float", - "shy-scissors-joke", - "small-eggs-learn", - "small-snails-mate", - "smooth-chefs-flow", - "spotty-penguins-wash", - "strange-clouds-pretend", - "strange-coins-hug", - "strange-frogs-remain", - "strange-steaks-pull", - "ten-turkeys-poke", - "tender-dots-warn", - "tender-pigs-rest", - "thin-ligers-pull", - "thirty-countries-refuse", - "tidy-impalas-refuse", - "tough-zoos-refuse", - "tricky-paws-march", - "twelve-spoons-play", - "twenty-impalas-count", - "two-lions-promise", - "two-walls-fetch", - "unlucky-hats-exist", - "unlucky-points-matter", - "violet-wombats-try", - "weak-moles-destroy", - "wise-moose-argue", - "yellow-houses-talk", - "yellow-seahorses-lick", - "young-lies-cry" - ] + "mode": "pre", + "tag": "beta", + "initialVersions": { + "comet-demo-admin": "1.0.0", + "comet-demo-admin-server": "1.0.0", + "comet-demo-api": "1.0.0", + "comet-demo-site": "1.0.0", + "comet-demo-site-pages": "1.0.0", + "comet-docs": "0.0.0", + "@comet/admin": "6.10.0", + "@comet/admin-babel-preset": "6.10.0", + "@comet/admin-color-picker": "6.10.0", + "@comet/admin-date-time": "6.10.0", + "@comet/admin-icons": "6.10.0", + "@comet/admin-react-select": "6.10.0", + "@comet/admin-rte": "6.10.0", + "@comet/admin-theme": "6.10.0", + "@comet/blocks-admin": "6.10.0", + "@comet/cms-admin": "6.10.0", + "@comet/blocks-api": "6.10.0", + "@comet/cms-api": "6.10.0", + "@comet/cli": "6.10.0", + "@comet/eslint-config": "6.10.0", + "@comet/eslint-plugin": "6.10.0", + "@comet/cms-site": "6.10.0", + "comet-storybook": "1.0.0" + }, + "changesets": [ + "blue-apricots-appear", + "brave-kiwis-pay", + "bright-dolls-live", + "brown-kids-develop", + "chatty-walls-study", + "chilled-walls-shop", + "clean-apes-vanish", + "clean-insects-happen", + "clever-clocks-smile", + "cool-kangaroos-cough", + "cool-kiwis-shop", + "curly-pillows-decide", + "curvy-moles-punch", + "cyan-drinks-enjoy", + "cyan-ladybugs-bathe", + "dirty-rabbits-tease", + "early-news-attend", + "eight-rules-laugh", + "eighty-owls-cheat", + "empty-islands-confess", + "fair-waves-breathe", + "fast-dodos-compete", + "fast-pens-laugh", + "fifty-keys-sit", + "fifty-suits-drive", + "fluffy-oranges-approve", + "fluffy-sheep-hammer", + "forty-bees-exercise", + "forty-hounds-work", + "fresh-buckets-begin", + "fresh-sheep-remember", + "friendly-mayflies-play", + "funny-paws-dream", + "fuzzy-foxes-laugh", + "gentle-chefs-sell", + "gentle-pots-perform", + "giant-apples-cheer", + "giant-humans-lick", + "giant-ladybugs-greet", + "giant-suns-begin", + "good-squids-join", + "great-hotels-unite", + "great-insects-reply", + "grumpy-poets-own", + "happy-fans-pump", + "happy-ladybugs-smile", + "hip-garlics-press", + "hot-buckets-talk", + "hot-carpets-eat", + "hot-drinks-crash", + "khaki-cobras-trade", + "khaki-eels-try", + "large-mirrors-teach", + "light-chefs-lie", + "light-ligers-wonder", + "long-bottles-brush", + "long-crabs-bake", + "long-items-sell", + "many-rivers-exist", + "mean-coats-matter", + "metal-bugs-beg", + "metal-seas-listen", + "modern-glasses-sort", + "nervous-sloths-thank", + "nervous-windows-grin", + "new-chicken-search", + "ninety-adults-peel", + "plenty-cougars-warn", + "polite-ducks-juggle", + "polite-needles-unite", + "poor-dragons-sing", + "popular-ducks-vanish", + "popular-planes-tap", + "popular-shirts-pretend", + "pretty-deers-trade", + "pretty-kangaroos-repair", + "proud-cheetahs-sleep", + "quick-foxes-notice", + "rare-boxes-guess", + "rare-crabs-burn", + "real-roses-applaud", + "rotten-clocks-shop", + "selfish-books-fail", + "selfish-dolls-beg", + "selfish-shrimps-explode", + "serious-bats-boil", + "serious-ravens-matter", + "sharp-zebras-hear", + "short-donkeys-double", + "shy-jars-float", + "shy-scissors-joke", + "small-eggs-learn", + "small-snails-mate", + "smooth-chefs-flow", + "spotty-penguins-wash", + "strange-clouds-pretend", + "strange-coins-hug", + "strange-frogs-remain", + "strange-steaks-pull", + "ten-turkeys-poke", + "tender-dots-warn", + "tender-pigs-rest", + "thin-ligers-pull", + "thirty-countries-refuse", + "tidy-impalas-refuse", + "tough-zoos-refuse", + "tricky-paws-march", + "twelve-spoons-play", + "twenty-impalas-count", + "two-lions-promise", + "two-walls-fetch", + "unlucky-hats-exist", + "unlucky-points-matter", + "violet-wombats-try", + "weak-moles-destroy", + "wise-moose-argue", + "yellow-houses-talk", + "yellow-seahorses-lick", + "young-lies-cry" + ] } diff --git a/.changeset/proud-dolphins-pump.md b/.changeset/proud-dolphins-pump.md new file mode 100644 index 0000000000..b4f97a2df0 --- /dev/null +++ b/.changeset/proud-dolphins-pump.md @@ -0,0 +1,7 @@ +--- +"@comet/admin": minor +--- + +Add `color` prop to `CometLogo` + +It now supports a colored and a white version of the logo. diff --git a/.changeset/rotten-clocks-shop.md b/.changeset/rotten-clocks-shop.md index a2a4f5dbd2..7b78e1485f 100644 --- a/.changeset/rotten-clocks-shop.md +++ b/.changeset/rotten-clocks-shop.md @@ -4,4 +4,4 @@ Add `GridColumnsButton` -This button opens a panel to hide or show columns of `DataGrid`, similar to MUIs `GridToolbarColumnsButton`. +This button opens a panel to hide or show columns of `DataGrid`, similar to MUIs `GridToolbarColumnsButton`. diff --git a/.changeset/selfish-dolls-beg.md b/.changeset/selfish-dolls-beg.md index fe70825e6b..32bff15360 100644 --- a/.changeset/selfish-dolls-beg.md +++ b/.changeset/selfish-dolls-beg.md @@ -8,7 +8,7 @@ This works both server-side (SSR, SSG) and client-side (block preview). New Apis: -- `recursivelyLoadBlockData`: used to call loaders for a block data tree -- `BlockLoader`: type of a loader function that is responsible for one block -- `useBlockPreviewFetch`: helper hook for block preview that creates client-side caching graphQLFetch/fetch -- `BlockLoaderDependencies`: interface with dependencies that get passed through recursivelyLoadBlockData into loader functions. Can be extended using module augmentation in application to inject eg. pageTreeNodeId. \ No newline at end of file +- `recursivelyLoadBlockData`: used to call loaders for a block data tree +- `BlockLoader`: type of a loader function that is responsible for one block +- `useBlockPreviewFetch`: helper hook for block preview that creates client-side caching graphQLFetch/fetch +- `BlockLoaderDependencies`: interface with dependencies that get passed through recursivelyLoadBlockData into loader functions. Can be extended using module augmentation in application to inject eg. pageTreeNodeId. diff --git a/.changeset/short-donkeys-double.md b/.changeset/short-donkeys-double.md index 0e63f464a5..e9f1bf341f 100644 --- a/.changeset/short-donkeys-double.md +++ b/.changeset/short-donkeys-double.md @@ -11,6 +11,6 @@ Add `MenuItemGroup` component to group menu items } to="/menu-item-1" /> } to="/menu-item-2" /> } to="/menu-item-3" /> - { /* Some more menu items... */ } + {/* Some more menu items... */} -``` \ No newline at end of file +``` diff --git a/.changeset/small-eggs-learn.md b/.changeset/small-eggs-learn.md index 46f9648586..a6e22563b8 100644 --- a/.changeset/small-eggs-learn.md +++ b/.changeset/small-eggs-learn.md @@ -10,7 +10,7 @@ API Generator: Replace graphql-type-json with graphql-scalars for JSON columns 2. Uninstall graphql-type-json: `npm install graphql-type-json` 3. Update imports: - ```diff - - import { GraphQLJSONObject } from "graphql-type-json"; - + import { GraphQLJSONObject } from "graphql-scalars"; - ``` + ```diff + - import { GraphQLJSONObject } from "graphql-type-json"; + + import { GraphQLJSONObject } from "graphql-scalars"; + ``` diff --git a/.changeset/tender-pigs-rest.md b/.changeset/tender-pigs-rest.md index 716d36c023..4f761bfd73 100644 --- a/.changeset/tender-pigs-rest.md +++ b/.changeset/tender-pigs-rest.md @@ -18,4 +18,4 @@ Hint: To use the custom variants without getting a type error, you must adjust t + /// // ... -``` \ No newline at end of file +``` diff --git a/.changeset/weak-knives-doubt.md b/.changeset/weak-knives-doubt.md new file mode 100644 index 0000000000..3fd7f10d3b --- /dev/null +++ b/.changeset/weak-knives-doubt.md @@ -0,0 +1,8 @@ +--- +"@comet/admin": patch +--- + +Prevent unintended `width: 100%` on nested `InputBase` components inside `FieldContainer` and `Field` components + +`FieldContainer` (and therefore `Field`) needs to set the with of the `InputBase` it wraps to 100%. +This also caused deeply nested `InputBase` components, e.g., inside a `Dialog`, to get this `width` and break the styling of these components, as they are not intended to be styled by `FieldContainer`. diff --git a/.changeset/wise-moose-argue.md b/.changeset/wise-moose-argue.md index 92bc3caee2..89411e55ca 100644 --- a/.changeset/wise-moose-argue.md +++ b/.changeset/wise-moose-argue.md @@ -5,20 +5,25 @@ Add `helperIcon` prop to MenuItemGroup. Its intended purpose is to render an icon with a `Tooltip` behind the group section title, if the menu is not collapsed. ### Examples: + **Render only an icon:** + ```tsx -} -> +}> {/* ... */} ``` + **Render an icon with tooltip:** + ```tsx } + helperIcon={ + + + + } > {/* ... */} diff --git a/.changeset/young-lies-cry.md b/.changeset/young-lies-cry.md index 8ede23a758..45d31e62bb 100644 --- a/.changeset/young-lies-cry.md +++ b/.changeset/young-lies-cry.md @@ -4,4 +4,4 @@ Rework shadows -- Change shadows 1 - 4 +- Change shadows 1 - 4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 28a3e0463f..2f0032a6c1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -72,7 +72,6 @@ jobs: - name: Lint run: | - pnpm exec prettier --check "./*.{js,json,md,yml}" pnpm run lint # check for duplicate ids of formatted messages pnpm run intl:extract diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 35fef538c6..0000000000 --- a/.prettierignore +++ /dev/null @@ -1,20 +0,0 @@ -package-lock.json -.gitignore -*.lock -*.gql -*.dist -lang/ -blocks-admin-lang/ -cms-admin-lang/ -comet-admin-lang/ -.watchmanconfig -lib/ -build/ -dist/ -schema.json -fragmentTypes.json -lerna.json -storybook-static -schema.graphql -block-meta.json -*.generated.ts diff --git a/demo/admin/.prettierignore b/demo/admin/.prettierignore index b9c372e756..c87319e34e 100644 --- a/demo/admin/.prettierignore +++ b/demo/admin/.prettierignore @@ -1,8 +1,5 @@ -package-lock.json -.gitignore -*.lock -*.gql -*.dist -fragmentTypes.json -graphql.generated.ts -blocks-admin-lang/ \ No newline at end of file +block-meta.json +lang-compiled/ +lang/ +schema.json +src/fragmentTypes.json \ No newline at end of file diff --git a/demo/admin/lint-staged.config.js b/demo/admin/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/demo/admin/lint-staged.config.js +++ b/demo/admin/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/demo/admin/package.json b/demo/admin/package.json index 0586c55d45..dfaae0d51e 100644 --- a/demo/admin/package.json +++ b/demo/admin/package.json @@ -14,8 +14,9 @@ "intl:compile:comet": "formatjs compile-folder --format simple --ast lang/comet-lang lang-compiled/comet-lang", "intl:compile:comet-demo": "formatjs compile-folder --format simple --ast lang/comet-demo-lang/admin lang-compiled/comet-demo-lang-admin", "intl:extract": "formatjs extract \"src/**/*.ts*\" --ignore ./**.d.ts --out-file lang-extracted/en.json --format simple", - "lint": "run-s intl:compile && run-p gql:types generate-block-types && run-p lint:eslint lint:tsc && $npm_execpath lint:generated-files-not-modified", + "lint": "run-s intl:compile && run-p gql:types generate-block-types && run-p lint:prettier lint:eslint lint:tsc && $npm_execpath lint:generated-files-not-modified", "lint:eslint": "eslint --max-warnings 0 --config ./.eslintrc.cli.js --ext .ts,.tsx,.js,.jsx,.json,.md src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --project .", "lint:generated-files-not-modified": "$npm_execpath admin-generator && git diff --exit-code HEAD --", "start": "run-s intl:compile && run-p gql:types generate-block-types && dotenv -- chokidar --initial -s \"../../packages/admin/*/src/**\" -c \"kill-port $ADMIN_PORT && vite --force\"" diff --git a/demo/admin/src/pages/EditPage.tsx b/demo/admin/src/pages/EditPage.tsx index 29333b0919..96ef9366f0 100644 --- a/demo/admin/src/pages/EditPage.tsx +++ b/demo/admin/src/pages/EditPage.tsx @@ -3,6 +3,7 @@ import { Loading, MainContent, messages, RouterPrompt, Toolbar, ToolbarActions, import { ArrowLeft, Preview } from "@comet/admin-icons"; import { AdminComponentRoot, AdminTabLabel } from "@comet/blocks-admin"; import { + AzureAiTranslatorProvider, BlockPreviewWithTabs, ContentScopeIndicator, createUsePage, @@ -126,7 +127,7 @@ export const EditPage: React.FC = ({ id, category }) => { } return ( - <> + {hasChanges && ( { @@ -201,6 +202,6 @@ export const EditPage: React.FC = ({ id, category }) => { {dialogs} - + ); }; diff --git a/demo/api/.prettierignore b/demo/api/.prettierignore index b2bf16f6f6..fe35c77446 100644 --- a/demo/api/.prettierignore +++ b/demo/api/.prettierignore @@ -1,6 +1,2 @@ -package-lock.json -.gitignore -*.lock -*.gql -*.dist +dist/ block-meta.json \ No newline at end of file diff --git a/demo/api/lint-staged.config.js b/demo/api/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/demo/api/lint-staged.config.js +++ b/demo/api/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/demo/api/package.json b/demo/api/package.json index dc0e0ee527..6c4dbb0240 100644 --- a/demo/api/package.json +++ b/demo/api/package.json @@ -11,8 +11,9 @@ "db:migrate:prod": "$npm_execpath console:prod migrate --", "fixtures": "$npm_execpath console fixtures --", "fixtures:prod": "$npm_execpath console:prod fixtures --", - "lint": "run-p lint:eslint lint:tsc && $npm_execpath lint:generated-files-not-modified", + "lint": "run-p lint:prettier lint:eslint lint:tsc && $npm_execpath lint:generated-files-not-modified", "lint:eslint": "eslint --max-warnings 0 --ext .ts,.tsx,.js,.jsx,.json,.md src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --project ./tsconfig.lint.json", "lint:generated-files-not-modified": "$npm_execpath api-generator && git diff --exit-code HEAD --", "mikro-orm": "mikro-orm", diff --git a/demo/api/src/app.module.ts b/demo/api/src/app.module.ts index 46a476c081..df6ba72630 100644 --- a/demo/api/src/app.module.ts +++ b/demo/api/src/app.module.ts @@ -1,5 +1,6 @@ import { AccessLogModule, + AzureAiTranslatorModule, AzureOpenAiContentGenerationModule, BlobStorageModule, BlocksModule, @@ -154,6 +155,7 @@ export class AppModule { PredefinedPageModule, CronJobsModule, ProductsModule, + ...(config.azureAiTranslator ? [AzureAiTranslatorModule.register(config.azureAiTranslator)] : []), AccessLogModule.forRoot({ shouldLogRequest: ({ user }) => { // Ignore system user diff --git a/demo/api/src/config/config.ts b/demo/api/src/config/config.ts index 9e5d3958d7..c3e266d1cf 100644 --- a/demo/api/src/config/config.ts +++ b/demo/api/src/config/config.ts @@ -44,6 +44,14 @@ export function createConfig(processEnv: NodeJS.ProcessEnv) { ...cometConfig.dam, secret: envVars.DAM_SECRET, }, + azureAiTranslator: + envVars.AZURE_AI_TRANSLATOR_ENDPOINT && envVars.AZURE_AI_TRANSLATOR_KEY && envVars.AZURE_AI_TRANSLATOR_REGION + ? { + endpoint: envVars.AZURE_AI_TRANSLATOR_ENDPOINT, + key: envVars.AZURE_AI_TRANSLATOR_KEY, + region: envVars.AZURE_AI_TRANSLATOR_REGION, + } + : undefined, blob: { storage: { driver: envVars.BLOB_STORAGE_DRIVER, diff --git a/demo/api/src/config/environment-variables.ts b/demo/api/src/config/environment-variables.ts index 8f5a80e526..d25af8c098 100644 --- a/demo/api/src/config/environment-variables.ts +++ b/demo/api/src/config/environment-variables.ts @@ -1,6 +1,6 @@ import { BlobStorageConfig } from "@comet/cms-api"; import { Transform, Type } from "class-transformer"; -import { IsBoolean, IsInt, IsOptional, IsString, MinLength, ValidateIf } from "class-validator"; +import { IsBoolean, IsInt, IsOptional, IsString, IsUrl, MinLength, ValidateIf } from "class-validator"; export class EnvironmentVariables { @IsString() @@ -100,6 +100,18 @@ export class EnvironmentVariables { @ValidateIf(() => process.env.NODE_ENV === "production") CDN_ORIGIN_CHECK_SECRET: string; + @ValidateIf((v) => v.AZURE_AI_TRANSLATOR_KEY || v.AZURE_AI_TRANSLATOR_REGION) + @IsUrl() + AZURE_AI_TRANSLATOR_ENDPOINT?: string; + + @ValidateIf((v) => v.AZURE_AI_TRANSLATOR_ENDPOINT || v.AZURE_AI_TRANSLATOR_REGION) + @IsString() + AZURE_AI_TRANSLATOR_KEY?: string; + + @ValidateIf((v) => v.AZURE_AI_TRANSLATOR_ENDPOINT || v.AZURE_AI_TRANSLATOR_KEY) + @IsString() + AZURE_AI_TRANSLATOR_REGION?: string; + @ValidateIf((v) => v.AZURE_OPEN_AI_CONTENT_GENERATION_API_KEY || v.AZURE_OPEN_AI_CONTENT_GENERATION_DEPLOYMENT_ID) @IsString() AZURE_OPEN_AI_CONTENT_GENERATION_API_URL?: string; diff --git a/demo/site/.prettierignore b/demo/site/.prettierignore new file mode 100644 index 0000000000..6f2ceda040 --- /dev/null +++ b/demo/site/.prettierignore @@ -0,0 +1,5 @@ +.next/ +block-meta.json +lang-compiled/ +lang/ +preBuild/ \ No newline at end of file diff --git a/demo/site/lint-staged.config.js b/demo/site/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/demo/site/lint-staged.config.js +++ b/demo/site/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/demo/site/package.json b/demo/site/package.json index 8b47ca5b24..88a446587a 100644 --- a/demo/site/package.json +++ b/demo/site/package.json @@ -12,8 +12,9 @@ "gql:watch": "graphql-codegen --watch", "intl:extract": "formatjs extract \"src/**/*.ts*\" --ignore **/*.d.ts --out-file lang-extracted/en.json --format simple", "intl:compile": "formatjs compile-folder --format simple --ast lang/comet-demo-lang/site lang-compiled/", - "lint": "run-s intl:compile && run-p gql:types generate-block-types && run-p lint:eslint lint:tsc", + "lint": "run-s intl:compile && run-p gql:types generate-block-types && run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 --config ./.eslintrc.cli.js --ext .ts,.tsx,.js,.jsx,.json,.md src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --project .", "next-build": "next build", "serve": "NODE_ENV=production node server.js" diff --git a/demo/site/tsconfig.json b/demo/site/tsconfig.json index e862b4614b..77e956206e 100644 --- a/demo/site/tsconfig.json +++ b/demo/site/tsconfig.json @@ -1,48 +1,35 @@ { - "ts-node": { + "ts-node": { + "compilerOptions": { + "module": "commonjs" + } + }, "compilerOptions": { - "module": "commonjs" - } - }, - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "strict": false, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "strictNullChecks": true, - "baseUrl": "./", - "paths": { - "@src/*": [ - "src/*" - ] + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "strictNullChecks": true, + "baseUrl": "./", + "paths": { + "@src/*": ["src/*"] + }, + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] }, - "incremental": true, - "plugins": [ - { - "name": "next" - } - ] - }, - "include": [ - "next-env.d.ts", - "src/**/*.ts", - "src/**/*.tsx", - ".next/types/**/*.ts" - ], - "exclude": [ - "node_modules" - ] + "include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/docs/.prettierignore b/docs/.prettierignore new file mode 100644 index 0000000000..ff192b426a --- /dev/null +++ b/docs/.prettierignore @@ -0,0 +1,8 @@ +# Copy of .gitignore, remove once Prettier v3 is in use + +# Production +build + +# Generated files +.docusaurus +.cache-loader diff --git a/docs/docs/content-scope/index.md b/docs/docs/content-scope/index.md index 37557f189a..62fc2aa141 100644 --- a/docs/docs/content-scope/index.md +++ b/docs/docs/content-scope/index.md @@ -4,4 +4,3 @@ sidebar_position: 6 --- COMET DXP can be used to create both, content websites and data-driven applications. The usage of the content scope differs considerably between these two use cases. - diff --git a/docs/docs/core-concepts/index.md b/docs/docs/core-concepts/index.md index cf76c5897c..f059ae30f6 100644 --- a/docs/docs/core-concepts/index.md +++ b/docs/docs/core-concepts/index.md @@ -3,7 +3,7 @@ title: Core Concepts sidebar_position: 3 --- -On this page, you'll learn the core concepts of COMET DXP. These concepts represent the essential building blocks when developing COMET DXP applications. List the status of all processes: +On this page, you'll learn the core concepts of COMET DXP. These concepts represent the essential building blocks when developing COMET DXP applications. ## Page Tree diff --git a/docs/docs/deployment-of-libraries/index.md b/docs/docs/deployment-of-libraries/index.md deleted file mode 100644 index 98b1720244..0000000000 --- a/docs/docs/deployment-of-libraries/index.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Deployment of Libraries & Canary Releases -sidebar_position: 12 ---- - -All of our packages versions use [semantic versioning](https://semver.org/). To manage all of these on the side of our big monorepo, we use the tool "[lerna](https://github.com/lerna/lerna)." - -## Canary Releases - -Every pull request merged in one of our protected branches will automatically trigger a canary release (`1.0.1-canary.8.0`) and be published by CI. - -## Stable versions - -When we want to post a new stable major/minor/patch, you only have to push a new tag with the next version (`1.0.0`), and CI will do the rest for you. It publishes the version of the tag to the registry. - -**Sample:** - - git tag 1.0.0 - git push --tags diff --git a/docs/docs/deployment/index.md b/docs/docs/deployment/index.md new file mode 100644 index 0000000000..1db73e11dc --- /dev/null +++ b/docs/docs/deployment/index.md @@ -0,0 +1,39 @@ +--- +title: Deployment +--- + +Deploying a Comet application requires a couple of [microservices](../overview/microservices.md). The following sections describe the necessary services and how to deploy them. + +## IDP + +Any OIDC-compliant Identity Provider (such as [Auth0](https://auth0.com/)) can be used with Comet DXP. + +## Database + +We recommend a managed PostgreSQL database such as [Azure Database for PostgreSQL](https://azure.microsoft.com/en-us/products/postgresql) or [PostgreSQL on Digital Ocean](https://www.digitalocean.com/pricing/managed-databases#postgresql). Managed databases are easier to maintain and scale as they provide automatic backups, monitoring, and scaling. + +For those with budget constraints, the database can be included in the [Docker Compose setup for the API, Admin, and Site](#docker-compose). + +## Asset Storage + +We recommend using a managed object storage service like [Azure Blob Storage](https://azure.microsoft.com/en-us/services/storage/blobs) or [Digital Ocean Spaces](https://www.digitalocean.com/products/spaces). + +## API, Admin, Site, oauth2-proxy and imgproxy + +There are several ways to deploy these microservices. The best deployment method depends on your budget and requirements. + +### Kubernetes + +Comet DXP is a cloud-native CMS, so Kubernetes is the preferred way to deploy a Comet application, as it provides the most flexibility and supports all enterprise requirements. However, it is usually the most expensive way. + +We provide [Helm](https://helm.sh/) Charts, which are available on [GitHub](https://github.com/vivid-planet/comet-charts), for easy deployment. + +### Serverless + +Comet Applications can also be deployed to serverless container platforms like [Azure Container Apps](https://azure.microsoft.com/en-us/products/container-apps) or [Digital Ocean App Platform](https://docs.digitalocean.com/products/app-platform/). + +It's important to note that deploying to a serverless platform comes with its own set of limitations. For instance, you won't be able to use the [CronJobModule](../cron-jobs/index.md) and the KubernetesModule in this setup. + +### Docker Compose + +For those with budget constraints, Docker Compose can be a viable option for deploying Comet applications. diff --git a/docs/docs/overview/index.md b/docs/docs/overview/index.md index 78077d3a96..055e2950da 100644 --- a/docs/docs/overview/index.md +++ b/docs/docs/overview/index.md @@ -35,3 +35,12 @@ Many of the highlighted microservices can be exchanged or omitted. - We want a solution that is highly customizable - We want to offer excellent developer experience (DX) - We want to host on-premise + +You can build two types of applications with COMET DXP: + +- **Content websites**: Websites that are primarily content-driven without a lot of structured data. Content websites heavily use the CMS features (page tree, blocks etc.) and have at least one site. +- **Data-driven applications**: Applications that are primarily data-driven. Data-driven applications might not use the CMS features and might not have a site at all. + +:::note +This terms are used throughout the documentation as some concepts heavily differ between this two types. +::: diff --git a/docs/docs/overview/microservices.md b/docs/docs/overview/microservices.md index e831719d05..882c5b2ca0 100644 --- a/docs/docs/overview/microservices.md +++ b/docs/docs/overview/microservices.md @@ -27,4 +27,4 @@ Other core libraries in use: Consumer for the data. This part is optional because we are headless. Also, multiple sites for different clients (e.g., website and mobile app) can coexist. -While any technology can be used, we focus on [NextJS](https://nextjs.org/). With NextJS we can use Typescript and React for building our sites. NextJS provides Server Side Rendering (SSR), Client Side Rendering (CSR), and [Static Generation](https://nextjs.org/docs/basic-features/pages#pre-rendering) (SG). We focus on SG to optimize for speed while avoiding maintaining a cache. +While any technology can be used, we focus on [NextJS](https://nextjs.org/). With NextJS we can use Typescript and React for building our sites. NextJS provides Server Side Rendering (SSR), Client Side Rendering (CSR), and [Static Generation](https://nextjs.org/docs/basic-features/pages#pre-rendering) (SG). diff --git a/docs/lint-staged.config.js b/docs/lint-staged.config.js new file mode 100644 index 0000000000..f34ef2ec57 --- /dev/null +++ b/docs/lint-staged.config.js @@ -0,0 +1,5 @@ +module.exports = { + "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", +}; diff --git a/docs/package.json b/docs/package.json index 8c1b500773..801b27f8c0 100644 --- a/docs/package.json +++ b/docs/package.json @@ -7,8 +7,9 @@ "clear": "docusaurus clear", "deploy": "docusaurus deploy", "docusaurus": "docusaurus", - "lint": "pnpm run lint:eslint", + "lint": "run-p lint:prettier lint:eslint", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "serve": "docusaurus serve", "start": "docusaurus start --port 3300", "swizzle": "docusaurus swizzle", @@ -75,6 +76,7 @@ "@tsconfig/docusaurus": "^1.0.5", "@types/react": "^17.0.48", "@types/react-dom": "^17.0.17", + "npm-run-all": "^4.1.5", "prettier": "^2.6.2", "sucrase": "^3.32.0", "typescript": "^4.7.4", diff --git a/lint-staged.config.js b/lint-staged.config.js index 83d0f0b40d..9f89fb469d 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -1,3 +1,3 @@ module.exports = { - "./*{js,json,md,yml}": "pnpm exec prettier --check", + "./!(demo|docs|packages|storybook)/**/*.{js,json,md,yml,yaml}": () => "pnpm lint:root", }; diff --git a/package.json b/package.json index 6dc1af76a2..ccb4312c18 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "dev:demo:site": "dev-pm start @demo-site", "postinstall": "husky install", "intl:extract": "formatjs extract './packages/admin/**/*.ts*' --out-file 'lang/en.json' --ignore './**.d.ts' --ignore './**.d.ts.map' --format simple --throws", - "lint": "pnpm recursive run lint", + "lint": "pnpm lint:root && pnpm recursive run lint", + "lint:root": "$npm_execpath prettier --check './!(demo|docs|packages|storybook)/**/*.{js,json,md,yml,yaml}'", "lint:eslint": "pnpm recursive run lint:eslint", "lint:tsc": "pnpm recursive run lint:tsc", "storybook": "pnpm --filter comet-storybook run storybook", diff --git a/packages/admin/admin-babel-preset/CHANGELOG.md b/packages/admin/admin-babel-preset/CHANGELOG.md index 1901b99b8f..4480c7359f 100644 --- a/packages/admin/admin-babel-preset/CHANGELOG.md +++ b/packages/admin/admin-babel-preset/CHANGELOG.md @@ -14,6 +14,8 @@ ## 7.0.0-beta.0 +## 6.16.0 + ## 6.15.1 ## 6.15.0 diff --git a/packages/admin/admin-color-picker/.prettierignore b/packages/admin/admin-color-picker/.prettierignore new file mode 100644 index 0000000000..f1ff06d608 --- /dev/null +++ b/packages/admin/admin-color-picker/.prettierignore @@ -0,0 +1 @@ +lib/ \ No newline at end of file diff --git a/packages/admin/admin-color-picker/CHANGELOG.md b/packages/admin/admin-color-picker/CHANGELOG.md index feee13492a..8db4a50a44 100644 --- a/packages/admin/admin-color-picker/CHANGELOG.md +++ b/packages/admin/admin-color-picker/CHANGELOG.md @@ -201,6 +201,15 @@ - @comet/admin@7.0.0-beta.0 - @comet/admin-icons@7.0.0-beta.0 +## 6.16.0 + +### Patch Changes + +- Updated dependencies [fb0fe2539] +- Updated dependencies [747fe32cc] + - @comet/admin@6.16.0 + - @comet/admin-icons@6.16.0 + ## 6.15.1 ### Patch Changes diff --git a/packages/admin/admin-color-picker/lint-staged.config.js b/packages/admin/admin-color-picker/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/admin/admin-color-picker/lint-staged.config.js +++ b/packages/admin/admin-color-picker/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/admin/admin-color-picker/package.json b/packages/admin/admin-color-picker/package.json index 2e651a1028..10fab2794a 100644 --- a/packages/admin/admin-color-picker/package.json +++ b/packages/admin/admin-color-picker/package.json @@ -17,8 +17,9 @@ "build:babel": "npx babel ./src -x \".ts,.tsx\" -d lib", "build:types": "tsc --project ./tsconfig.json --emitDeclarationOnly", "clean": "rimraf lib", - "lint": "run-p lint:eslint lint:tsc", + "lint": "run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit", "start": "run-p start:babel start:types", "start:babel": "npx babel ./src -x \".ts,.tsx\" -d lib -w", diff --git a/packages/admin/admin-date-time/.prettierignore b/packages/admin/admin-date-time/.prettierignore new file mode 100644 index 0000000000..f1ff06d608 --- /dev/null +++ b/packages/admin/admin-date-time/.prettierignore @@ -0,0 +1 @@ +lib/ \ No newline at end of file diff --git a/packages/admin/admin-date-time/CHANGELOG.md b/packages/admin/admin-date-time/CHANGELOG.md index f6edacb3cb..daacfc34b0 100644 --- a/packages/admin/admin-date-time/CHANGELOG.md +++ b/packages/admin/admin-date-time/CHANGELOG.md @@ -258,6 +258,15 @@ - @comet/admin@7.0.0-beta.0 - @comet/admin-icons@7.0.0-beta.0 +## 6.16.0 + +### Patch Changes + +- Updated dependencies [fb0fe2539] +- Updated dependencies [747fe32cc] + - @comet/admin@6.16.0 + - @comet/admin-icons@6.16.0 + ## 6.15.1 ### Patch Changes diff --git a/packages/admin/admin-date-time/lint-staged.config.js b/packages/admin/admin-date-time/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/admin/admin-date-time/lint-staged.config.js +++ b/packages/admin/admin-date-time/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/admin/admin-date-time/package.json b/packages/admin/admin-date-time/package.json index e09c6908e9..e2f80d7e64 100644 --- a/packages/admin/admin-date-time/package.json +++ b/packages/admin/admin-date-time/package.json @@ -17,8 +17,9 @@ "build:babel": "npx babel ./src -x \".ts,.tsx\" -d lib", "build:types": "tsc --project ./tsconfig.json --emitDeclarationOnly", "clean": "rimraf lib", - "lint": "run-p lint:eslint lint:tsc", + "lint": "run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit", "start": "run-p start:babel start:types", "start:babel": "npx babel ./src -x \".ts,.tsx\" -d lib -w", diff --git a/packages/admin/admin-icons/.prettierignore b/packages/admin/admin-icons/.prettierignore new file mode 100644 index 0000000000..f1ff06d608 --- /dev/null +++ b/packages/admin/admin-icons/.prettierignore @@ -0,0 +1 @@ +lib/ \ No newline at end of file diff --git a/packages/admin/admin-icons/CHANGELOG.md b/packages/admin/admin-icons/CHANGELOG.md index 60a225f8c4..2b6ba27535 100644 --- a/packages/admin/admin-icons/CHANGELOG.md +++ b/packages/admin/admin-icons/CHANGELOG.md @@ -14,6 +14,8 @@ ## 7.0.0-beta.0 +## 6.16.0 + ## 6.15.1 ## 6.15.0 diff --git a/packages/admin/admin-icons/lint-staged.config.js b/packages/admin/admin-icons/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/admin/admin-icons/lint-staged.config.js +++ b/packages/admin/admin-icons/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/admin/admin-icons/package.json b/packages/admin/admin-icons/package.json index 73effb218e..38a3186cf4 100644 --- a/packages/admin/admin-icons/package.json +++ b/packages/admin/admin-icons/package.json @@ -14,8 +14,9 @@ "build:types": "tsc --project ./tsconfig.json --emitDeclarationOnly", "clean": "rimraf lib", "generate-icons": "ts-node generate-icons.ts", - "lint": "run-p lint:eslint lint:tsc", + "lint": "run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit", "start": "$npm_execpath run generate-icons && run-p start:babel start:types", "start:babel": "npx babel ./src -x \".ts,.tsx\" -d lib -w", diff --git a/packages/admin/admin-react-select/.prettierignore b/packages/admin/admin-react-select/.prettierignore new file mode 100644 index 0000000000..f1ff06d608 --- /dev/null +++ b/packages/admin/admin-react-select/.prettierignore @@ -0,0 +1 @@ +lib/ \ No newline at end of file diff --git a/packages/admin/admin-react-select/CHANGELOG.md b/packages/admin/admin-react-select/CHANGELOG.md index c11e055300..4f29931cb2 100644 --- a/packages/admin/admin-react-select/CHANGELOG.md +++ b/packages/admin/admin-react-select/CHANGELOG.md @@ -198,6 +198,15 @@ - Updated dependencies [92eae2ba9] - @comet/admin@7.0.0-beta.0 +## 6.16.0 + +### Patch Changes + +- Updated dependencies [fb0fe2539] +- Updated dependencies [747fe32cc] + - @comet/admin@6.16.0 + - @comet/admin-icons@6.16.0 + ## 6.15.1 ### Patch Changes diff --git a/packages/admin/admin-react-select/lint-staged.config.js b/packages/admin/admin-react-select/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/admin/admin-react-select/lint-staged.config.js +++ b/packages/admin/admin-react-select/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/admin/admin-react-select/package.json b/packages/admin/admin-react-select/package.json index eb3449910e..2a71c3c256 100644 --- a/packages/admin/admin-react-select/package.json +++ b/packages/admin/admin-react-select/package.json @@ -17,8 +17,9 @@ "build:babel": "npx babel ./src -x \".ts,.tsx\" -d lib", "build:types": "tsc --project ./tsconfig.json --emitDeclarationOnly", "clean": "rimraf lib", - "lint": "run-p lint:eslint lint:tsc", + "lint": "run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit", "start": "run-p start:babel start:types", "start:babel": "npx babel ./src -x \".ts,.tsx\" -d lib -w", diff --git a/packages/admin/admin-rte/.prettierignore b/packages/admin/admin-rte/.prettierignore new file mode 100644 index 0000000000..f1ff06d608 --- /dev/null +++ b/packages/admin/admin-rte/.prettierignore @@ -0,0 +1 @@ +lib/ \ No newline at end of file diff --git a/packages/admin/admin-rte/CHANGELOG.md b/packages/admin/admin-rte/CHANGELOG.md index be6b7287dc..3be9616b6d 100644 --- a/packages/admin/admin-rte/CHANGELOG.md +++ b/packages/admin/admin-rte/CHANGELOG.md @@ -196,6 +196,15 @@ - @comet/admin@7.0.0-beta.0 - @comet/admin-icons@7.0.0-beta.0 +## 6.16.0 + +### Patch Changes + +- Updated dependencies [fb0fe2539] +- Updated dependencies [747fe32cc] + - @comet/admin@6.16.0 + - @comet/admin-icons@6.16.0 + ## 6.15.1 ### Patch Changes diff --git a/packages/admin/admin-rte/lint-staged.config.js b/packages/admin/admin-rte/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/admin/admin-rte/lint-staged.config.js +++ b/packages/admin/admin-rte/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/admin/admin-rte/package.json b/packages/admin/admin-rte/package.json index fb76893ed5..b826d1734b 100644 --- a/packages/admin/admin-rte/package.json +++ b/packages/admin/admin-rte/package.json @@ -17,8 +17,9 @@ "build:babel": "npx babel ./src -x \".ts,.tsx\" -d lib", "build:types": "tsc --project ./tsconfig.json --emitDeclarationOnly", "clean": "rimraf lib", - "lint": "run-p lint:eslint lint:tsc", + "lint": "run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit", "start": "run-p start:babel start:types", "start:babel": "npx babel ./src -x \".ts,.tsx\" -d lib -w", diff --git a/packages/admin/admin-theme/.prettierignore b/packages/admin/admin-theme/.prettierignore new file mode 100644 index 0000000000..f1ff06d608 --- /dev/null +++ b/packages/admin/admin-theme/.prettierignore @@ -0,0 +1 @@ +lib/ \ No newline at end of file diff --git a/packages/admin/admin-theme/CHANGELOG.md b/packages/admin/admin-theme/CHANGELOG.md index dd9d45f801..2880c27913 100644 --- a/packages/admin/admin-theme/CHANGELOG.md +++ b/packages/admin/admin-theme/CHANGELOG.md @@ -218,6 +218,12 @@ - @comet/admin-icons@7.0.0-beta.0 +## 6.16.0 + +### Patch Changes + +- @comet/admin-icons@6.16.0 + ## 6.15.1 ### Patch Changes diff --git a/packages/admin/admin-theme/lint-staged.config.js b/packages/admin/admin-theme/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/admin/admin-theme/lint-staged.config.js +++ b/packages/admin/admin-theme/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/admin/admin-theme/package.json b/packages/admin/admin-theme/package.json index 342a180df9..f49f4ba446 100644 --- a/packages/admin/admin-theme/package.json +++ b/packages/admin/admin-theme/package.json @@ -17,8 +17,9 @@ "build:babel": "npx babel ./src -x \".ts,.tsx\" -d lib", "build:types": "tsc --project ./tsconfig.json --emitDeclarationOnly", "clean": "rimraf lib", - "lint": "run-p lint:eslint lint:tsc", + "lint": "run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit", "start": "run-p start:babel start:types", "start:babel": "npx babel ./src -x \".ts,.tsx\" -d lib -w", diff --git a/packages/admin/admin/.babelrc.json b/packages/admin/admin/.babelrc.json index 97e5794d96..b930acf95a 100644 --- a/packages/admin/admin/.babelrc.json +++ b/packages/admin/admin/.babelrc.json @@ -1,6 +1,4 @@ { "presets": ["@comet/admin-babel-preset"], - "ignore": [ - "**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/__tests__/**" - ] + "ignore": ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/__tests__/**"] } diff --git a/packages/admin/admin/.prettierignore b/packages/admin/admin/.prettierignore new file mode 100644 index 0000000000..f1ff06d608 --- /dev/null +++ b/packages/admin/admin/.prettierignore @@ -0,0 +1 @@ +lib/ \ No newline at end of file diff --git a/packages/admin/admin/CHANGELOG.md b/packages/admin/admin/CHANGELOG.md index 41603811e8..a696b75610 100644 --- a/packages/admin/admin/CHANGELOG.md +++ b/packages/admin/admin/CHANGELOG.md @@ -417,6 +417,17 @@ - @comet/admin-theme@7.0.0-beta.0 - @comet/admin-icons@7.0.0-beta.0 +## 6.16.0 + +### Minor Changes + +- fb0fe2539: Add `FinalFormNumberInput` and `NumberField` as optimised fields for number inputs in FinalForms + +### Patch Changes + +- 747fe32cc: Fix incorrect router prompt in `TableLocalChanges` when there are no changes + - @comet/admin-icons@6.16.0 + ## 6.15.1 ### Patch Changes diff --git a/packages/admin/admin/lint-staged.config.js b/packages/admin/admin/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/admin/admin/lint-staged.config.js +++ b/packages/admin/admin/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/admin/admin/package.json b/packages/admin/admin/package.json index aa130f21c9..a90f6d2b59 100644 --- a/packages/admin/admin/package.json +++ b/packages/admin/admin/package.json @@ -17,8 +17,9 @@ "build:babel": "npx babel ./src -x \".ts,.tsx\" -d lib", "build:types": "tsc --project ./tsconfig.build.json --emitDeclarationOnly", "clean": "rimraf lib", - "lint": "run-p lint:eslint lint:tsc", + "lint": "run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit", "start": "run-p start:babel start:types", "start:babel": "npx babel ./src -x \".ts,.tsx\" -d lib -w", diff --git a/packages/admin/admin/src/common/CometLogo.tsx b/packages/admin/admin/src/common/CometLogo.tsx index 649bf7779d..784c297a76 100644 --- a/packages/admin/admin/src/common/CometLogo.tsx +++ b/packages/admin/admin/src/common/CometLogo.tsx @@ -1,6 +1,10 @@ import * as React from "react"; -export function CometLogo(): React.ReactElement { +interface CometLogoProps { + color?: "colored" | "white"; +} + +export function CometLogo({ color = "colored" }: CometLogoProps): React.ReactElement { return ( @@ -11,7 +15,7 @@ export function CometLogo(): React.ReactElement { diff --git a/packages/admin/admin/src/error/errordialog/createErrorDialogApolloLink.tsx b/packages/admin/admin/src/error/errordialog/createErrorDialogApolloLink.tsx index cf7aa871aa..27b7d0f70a 100644 --- a/packages/admin/admin/src/error/errordialog/createErrorDialogApolloLink.tsx +++ b/packages/admin/admin/src/error/errordialog/createErrorDialogApolloLink.tsx @@ -22,7 +22,7 @@ export const createErrorDialogApolloLink = (config?: { signInUrl?: string }) => if (graphQLErrors) { if (graphQLErrors.some((e) => e.message === "UNAUTHENTICATED")) { errorType = "unauthenticated"; // Error is triggered by Comet Guard - } else if (graphQLErrors.some((e) => e.extensions.response.statusCode === StatusCodes.UNAUTHORIZED)) { + } else if (graphQLErrors.some((e) => e.extensions.response?.statusCode === StatusCodes.UNAUTHORIZED)) { errorType = "unauthorized"; // Error is triggered by UnauthorizedException } else { errorType = "graphql"; diff --git a/packages/admin/admin/src/form/FinalFormNumberInput.tsx b/packages/admin/admin/src/form/FinalFormNumberInput.tsx new file mode 100644 index 0000000000..2dda2e9759 --- /dev/null +++ b/packages/admin/admin/src/form/FinalFormNumberInput.tsx @@ -0,0 +1,105 @@ +import { InputBase, InputBaseProps } from "@mui/material"; +import * as React from "react"; +import { FieldRenderProps } from "react-final-form"; +import { useIntl } from "react-intl"; + +import { ClearInputAdornment } from "../common/ClearInputAdornment"; + +export type FinalFormNumberInputProps = InputBaseProps & + FieldRenderProps & { + clearable?: boolean; + decimals?: number; + }; + +export function FinalFormNumberInput({ + meta, + input, + innerRef, + clearable, + endAdornment, + decimals = 0, + ...props +}: FinalFormNumberInputProps): React.ReactElement { + const intl = useIntl(); + + const [formattedNumberValue, setFormattedNumberValue] = React.useState(""); + + const getFormattedValue = React.useCallback( + (value: number | undefined) => { + const formattedValue = + value !== undefined ? intl.formatNumber(value, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) : ""; + return formattedValue; + }, + [decimals, intl], + ); + + const handleChange = (event: React.ChangeEvent) => { + const { value } = event.target; + setFormattedNumberValue(value); + }; + + const updateFormattedNumberValue = React.useCallback( + (inputValue?: number) => { + if (!inputValue && inputValue !== 0) { + input.onChange(undefined); + setFormattedNumberValue(""); + } else { + setFormattedNumberValue(getFormattedValue(inputValue)); + } + }, + [getFormattedValue, input], + ); + + const handleBlur = (event: React.FocusEvent) => { + const { value } = event.target; + const numberParts = intl.formatNumberToParts(1111.111); + const decimalSymbol = numberParts.find(({ type }) => type === "decimal")?.value; + const thousandSeparatorSymbol = numberParts.find(({ type }) => type === "group")?.value; + + const numericValue = parseFloat( + value + .split(thousandSeparatorSymbol || "") + .join("") + .split(decimalSymbol || ".") + .join("."), + ); + + const roundToDecimals = (numericValue: number, decimals: number) => { + const factor = Math.pow(10, decimals); + return Math.round(numericValue * factor) / factor; + }; + + const inputValue: number | undefined = isNaN(numericValue) ? undefined : roundToDecimals(numericValue, decimals); + input.onChange(inputValue); + + if (input.value === inputValue) { + updateFormattedNumberValue(inputValue); + } + }; + + React.useEffect(() => { + updateFormattedNumberValue(input.value); + }, [updateFormattedNumberValue, input]); + + return ( + + {endAdornment} + {clearable && ( + input.onChange(undefined)} + /> + )} + + } + /> + ); +} diff --git a/packages/admin/admin/src/form/fields/NumberField.tsx b/packages/admin/admin/src/form/fields/NumberField.tsx new file mode 100644 index 0000000000..747bf6258e --- /dev/null +++ b/packages/admin/admin/src/form/fields/NumberField.tsx @@ -0,0 +1,10 @@ +import * as React from "react"; + +import { Field, FieldProps } from "../Field"; +import { FinalFormNumberInput } from "../FinalFormNumberInput"; + +export type NumberFieldProps = FieldProps; + +export const NumberField = ({ ...restProps }: NumberFieldProps) => { + return ; +}; diff --git a/packages/admin/admin/src/index.ts b/packages/admin/admin/src/index.ts index 461e1f22d5..e43afd9ff3 100644 --- a/packages/admin/admin/src/index.ts +++ b/packages/admin/admin/src/index.ts @@ -82,6 +82,7 @@ export { AsyncAutocompleteField, AsyncAutocompleteFieldProps } from "./form/fiel export { AsyncSelectField, AsyncSelectFieldProps } from "./form/fields/AsyncSelectField"; export { AutocompleteField, AutocompleteFieldProps } from "./form/fields/AutocompleteField"; export { CheckboxField, CheckboxFieldProps } from "./form/fields/CheckboxField"; +export { NumberField, NumberFieldProps } from "./form/fields/NumberField"; export { SearchField, SearchFieldProps } from "./form/fields/SearchField"; export { SelectField, SelectFieldProps } from "./form/fields/SelectField"; export { SwitchField, SwitchFieldProps } from "./form/fields/SwitchField"; @@ -92,6 +93,7 @@ export { FinalFormAsyncSelect, FinalFormAsyncSelectProps } from "./form/FinalFor export { FinalFormContext, FinalFormContextProvider, FinalFormContextProviderProps, useFinalFormContext } from "./form/FinalFormContextProvider"; export { FinalFormFileSelect, FinalFormFileSelectClassKey, FinalFormFileSelectProps } from "./form/FinalFormFileSelect"; export { FinalFormInput, FinalFormInputProps } from "./form/FinalFormInput"; +export { FinalFormNumberInput, FinalFormNumberInputProps } from "./form/FinalFormNumberInput"; export { FinalFormRangeInput, FinalFormRangeInputClassKey, FinalFormRangeInputProps } from "./form/FinalFormRangeInput"; export { FinalFormSearchTextField, FinalFormSearchTextFieldProps } from "./form/FinalFormSearchTextField"; export { FinalFormSelect, FinalFormSelectProps } from "./form/FinalFormSelect"; diff --git a/packages/admin/admin/src/table/TableLocalChanges.tsx b/packages/admin/admin/src/table/TableLocalChanges.tsx index 49d3635050..4ee42ca832 100644 --- a/packages/admin/admin/src/table/TableLocalChanges.tsx +++ b/packages/admin/admin/src/table/TableLocalChanges.tsx @@ -86,10 +86,10 @@ export class TableLocalChanges message={() => { const isDirty = Object.keys(this.state.changes).length > 0 || this.state.changedOrder; if (isDirty) { - return true; + return "Do you want to save your changes?"; //TODO translate, we need intl context + //return intl.formatMessage(messages.saveUnsavedChanges); } - return "Do you want to save your changes?"; //TODO translate, we need intl context - //return intl.formatMessage(messages.saveUnsavedChanges); + return true; }} saveAction={async () => { await this.submitLocalDataChanges(); diff --git a/packages/admin/blocks-admin/.babelrc.json b/packages/admin/blocks-admin/.babelrc.json index 97e5794d96..b930acf95a 100644 --- a/packages/admin/blocks-admin/.babelrc.json +++ b/packages/admin/blocks-admin/.babelrc.json @@ -1,6 +1,4 @@ { "presets": ["@comet/admin-babel-preset"], - "ignore": [ - "**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/__tests__/**" - ] + "ignore": ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/__tests__/**"] } diff --git a/packages/admin/blocks-admin/.prettierignore b/packages/admin/blocks-admin/.prettierignore new file mode 100644 index 0000000000..960a54081c --- /dev/null +++ b/packages/admin/blocks-admin/.prettierignore @@ -0,0 +1,2 @@ +lib/ +block-meta.json \ No newline at end of file diff --git a/packages/admin/blocks-admin/CHANGELOG.md b/packages/admin/blocks-admin/CHANGELOG.md index d35becc0d9..3493a6f7e5 100644 --- a/packages/admin/blocks-admin/CHANGELOG.md +++ b/packages/admin/blocks-admin/CHANGELOG.md @@ -226,6 +226,15 @@ - @comet/admin@7.0.0-beta.0 - @comet/admin-icons@7.0.0-beta.0 +## 6.16.0 + +### Patch Changes + +- Updated dependencies [fb0fe2539] +- Updated dependencies [747fe32cc] + - @comet/admin@6.16.0 + - @comet/admin-icons@6.16.0 + ## 6.15.1 ### Patch Changes diff --git a/packages/admin/blocks-admin/lint-staged.config.js b/packages/admin/blocks-admin/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/admin/blocks-admin/lint-staged.config.js +++ b/packages/admin/blocks-admin/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/admin/blocks-admin/package.json b/packages/admin/blocks-admin/package.json index 827cc096a5..92c0eaaca5 100644 --- a/packages/admin/blocks-admin/package.json +++ b/packages/admin/blocks-admin/package.json @@ -19,8 +19,9 @@ "clean": "rimraf lib", "generate-block-types": "comet generate-block-types --inputs", "generate-block-types:watch": "chokidar -s \"**/block-meta.json\" -c \"$npm_execpath generate-block-types\"", - "lint": "$npm_execpath generate-block-types && run-p lint:eslint lint:tsc", + "lint": "$npm_execpath generate-block-types && run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit", "start": "$npm_execpath generate-block-types && run-p start:babel start:types", "start:babel": "npx babel ./src -x \".ts,.tsx\" -d lib -w", diff --git a/packages/admin/cms-admin/.babelrc.json b/packages/admin/cms-admin/.babelrc.json index 97e5794d96..b930acf95a 100644 --- a/packages/admin/cms-admin/.babelrc.json +++ b/packages/admin/cms-admin/.babelrc.json @@ -1,6 +1,4 @@ { "presets": ["@comet/admin-babel-preset"], - "ignore": [ - "**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/__tests__/**" - ] + "ignore": ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/__tests__/**"] } diff --git a/packages/admin/cms-admin/.prettierignore b/packages/admin/cms-admin/.prettierignore new file mode 100644 index 0000000000..960a54081c --- /dev/null +++ b/packages/admin/cms-admin/.prettierignore @@ -0,0 +1,2 @@ +lib/ +block-meta.json \ No newline at end of file diff --git a/packages/admin/cms-admin/CHANGELOG.md b/packages/admin/cms-admin/CHANGELOG.md index 98851af58e..acb3b4c2ae 100644 --- a/packages/admin/cms-admin/CHANGELOG.md +++ b/packages/admin/cms-admin/CHANGELOG.md @@ -626,6 +626,57 @@ - @comet/blocks-admin@7.0.0-beta.0 - @comet/admin-icons@7.0.0-beta.0 +## 6.16.0 + +### Minor Changes + +- 5e830f8d9: Add an [Azure AI Translator](https://azure.microsoft.com/en-us/products/ai-services/ai-translator) implementation of the content translation feature + + To use it, do the following: + + **API:** + + ```diff + // app.module.ts + export class AppModule { + static forRoot(config: Config): DynamicModule { + return { + imports: [ + // ... + + AzureAiTranslatorModule.register({ + + endpoint: envVars.AZURE_AI_TRANSLATOR_ENDPOINT, + + key: envVars.AZURE_AI_TRANSLATOR_KEY, + + region: envVars.AZURE_AI_TRANSLATOR_REGION, + + }), + ], + }; + } + } + ``` + + Users need the `translation` permission to use the translation feature. + + **Admin:** + + Wrap the section where you want to use the content translation with the `AzureAiTranslatorProvider` provider: + + ```tsx + {/* ... */} + ``` + + Note: `AzureAiTranslatorProvider` automatically checks for the `translation` permission. The translation button is only shown for users with this permission. + +### Patch Changes + +- Updated dependencies [fb0fe2539] +- Updated dependencies [747fe32cc] + - @comet/admin@6.16.0 + - @comet/admin-date-time@6.16.0 + - @comet/admin-icons@6.16.0 + - @comet/admin-rte@6.16.0 + - @comet/admin-theme@6.16.0 + - @comet/blocks-admin@6.16.0 + ## 6.15.1 ### Patch Changes diff --git a/packages/admin/cms-admin/lint-staged.config.js b/packages/admin/cms-admin/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/admin/cms-admin/lint-staged.config.js +++ b/packages/admin/cms-admin/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/admin/cms-admin/package.json b/packages/admin/cms-admin/package.json index 975d691124..e10cb2bda9 100644 --- a/packages/admin/cms-admin/package.json +++ b/packages/admin/cms-admin/package.json @@ -24,8 +24,9 @@ "generate-block-types:watch": "chokidar -s \"**/block-meta.json\" -c \"$npm_execpath generate-block-types\"", "generate-graphql-types": "graphql-codegen", "generate-graphql-types:watch": "$npm_execpath generate-graphql-types --watch", - "lint": "run-p generate-graphql-types generate-block-types && run-p lint:eslint lint:tsc", + "lint": "run-p generate-graphql-types generate-block-types && run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit", "start": "run-p generate-graphql-types generate-block-types && run-p start:babel start:types", "start:babel": "npx babel ./src -x \".ts,.tsx\" -d lib -w", diff --git a/packages/admin/cms-admin/src/common/header/Header.tsx b/packages/admin/cms-admin/src/common/header/Header.tsx index 5c23fe1782..6284fc0165 100644 --- a/packages/admin/cms-admin/src/common/header/Header.tsx +++ b/packages/admin/cms-admin/src/common/header/Header.tsx @@ -1,9 +1,7 @@ -import { AppHeader, AppHeaderFillSpace, AppHeaderMenuButton } from "@comet/admin"; +import { AppHeader, AppHeaderFillSpace, AppHeaderMenuButton, CometLogo } from "@comet/admin"; import { useMediaQuery, useTheme } from "@mui/material"; import * as React from "react"; -import { Logo } from "./Logo"; - interface Props { children?: React.ReactNode; logo?: React.ReactNode; @@ -16,7 +14,7 @@ function Header({ children, logo }: Props): React.ReactElement { return ( - {!isMobile && (logo || )} + {!isMobile && (logo || )} {children} diff --git a/packages/admin/cms-admin/src/common/header/Logo.tsx b/packages/admin/cms-admin/src/common/header/Logo.tsx deleted file mode 100644 index 71728d6de2..0000000000 --- a/packages/admin/cms-admin/src/common/header/Logo.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import * as React from "react"; - -function Logo(): React.ReactElement { - return ( - - - - ); -} - -export { Logo }; diff --git a/packages/admin/cms-admin/src/dam/FileForm/FileSettingsFields.tsx b/packages/admin/cms-admin/src/dam/FileForm/FileSettingsFields.tsx index a84e2b85e4..0b325a5b37 100644 --- a/packages/admin/cms-admin/src/dam/FileForm/FileSettingsFields.tsx +++ b/packages/admin/cms-admin/src/dam/FileForm/FileSettingsFields.tsx @@ -11,6 +11,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import { GQLLicenseType } from "../../graphql.generated"; import { useDamConfig } from "../config/useDamConfig"; import { useDamScope } from "../config/useDamScope"; +import { slugifyFilename } from "../helpers/slugifyFilename"; import { CropSettingsFields } from "./CropSettingsFields"; import { DamFileDetails, EditFileFormValues } from "./EditFile"; import { GQLDamIsFilenameOccupiedQuery, GQLDamIsFilenameOccupiedQueryVariables } from "./FileSettingsFields.generated"; @@ -96,6 +97,7 @@ export const FileSettingsFields = ({ file }: SettingsFormProps): React.ReactElem defaultMessage: "File Name", })} name="name" + endAdornment={`.${file.name.split(".").pop()}`} component={FinalFormInput} validate={async (value, allValues, meta) => { if (value && meta?.dirty) { @@ -107,6 +109,25 @@ export const FileSettingsFields = ({ file }: SettingsFormProps): React.ReactElem } } }} + onBlur={() => { + const filename: string | undefined = formApi.getFieldState("name")?.value; + const nameWithoutExtension = filename?.split(".").slice(0, -1).join("."); + const extension = file.name.split(".").pop(); + + if (nameWithoutExtension) { + // slugify can't happen on format because then it wouldn't be possible + // to type spaces and other special characters that are removed by slugify + formApi.change("name", slugifyFilename(nameWithoutExtension, extension)); + } + }} + format={(value: string) => { + const nameWithoutExtension = value.split(".").slice(0, -1).join("."); + return nameWithoutExtension; + }} + parse={(value: string) => { + const extension = file.name.split(".").pop(); + return `${value}.${extension}`; + }} fullWidth /> diff --git a/packages/admin/cms-admin/src/dam/helpers/slugifyFilename.ts b/packages/admin/cms-admin/src/dam/helpers/slugifyFilename.ts new file mode 100644 index 0000000000..525bc937e9 --- /dev/null +++ b/packages/admin/cms-admin/src/dam/helpers/slugifyFilename.ts @@ -0,0 +1,6 @@ +import slugify from "slugify"; + +export function slugifyFilename(filename: string, extension?: string): string { + const extensionWithDot = extension === undefined ? "" : extension.startsWith(".") ? extension : `.${extension}`; + return `${slugify(filename)}${extensionWithDot}`; +} diff --git a/packages/admin/cms-admin/src/index.ts b/packages/admin/cms-admin/src/index.ts index b929c6fc11..c41b61a101 100644 --- a/packages/admin/cms-admin/src/index.ts +++ b/packages/admin/cms-admin/src/index.ts @@ -108,6 +108,7 @@ export type { SiteConfig } from "./sitesConfig/SitesConfigContext"; export { SitesConfigProvider } from "./sitesConfig/SitesConfigProvider"; export { useSiteConfig } from "./sitesConfig/useSiteConfig"; export { useSitesConfig } from "./sitesConfig/useSitesConfig"; +export { AzureAiTranslatorProvider } from "./translation/AzureAiTranslatorProvider"; export { CurrentUserInterface, CurrentUserProvider, useCurrentUser, useUserPermissionCheck } from "./userPermissions/hooks/currentUser"; export { UserPermissionsPage } from "./userPermissions/UserPermissionsPage"; // eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-imports diff --git a/packages/admin/cms-admin/src/translation/AzureAiTranslatorProvider.tsx b/packages/admin/cms-admin/src/translation/AzureAiTranslatorProvider.tsx new file mode 100644 index 0000000000..923052155d --- /dev/null +++ b/packages/admin/cms-admin/src/translation/AzureAiTranslatorProvider.tsx @@ -0,0 +1,41 @@ +import { gql, useApolloClient } from "@apollo/client"; +import { ContentTranslationServiceProvider } from "@comet/admin"; +import React from "react"; + +import { useContentScope } from "../contentScope/Provider"; +import { useUserPermissionCheck } from "../userPermissions/hooks/currentUser"; +import { GQLTranslateQuery, GQLTranslateQueryVariables } from "./AzureAiTranslatorProvider.generated"; + +interface AzureAiTranslatorProps extends Omit, "enabled" | "translate"> { + enabled?: boolean; +} + +export const AzureAiTranslatorProvider = ({ children, enabled = false, ...rest }: React.PropsWithChildren) => { + const { scope } = useContentScope(); + const apolloClient = useApolloClient(); + const isAllowed = useUserPermissionCheck(); + + return ( + { + const { data } = await apolloClient.query({ + query: translationQuery, + variables: { + input: { text, targetLanguage: scope.language }, + }, + }); + return data.azureAiTranslate; + }} + > + {children} + + ); +}; + +const translationQuery = gql` + query Translate($input: AzureAiTranslationInput!) { + azureAiTranslate(input: $input) + } +`; diff --git a/packages/api/blocks-api/.prettierignore b/packages/api/blocks-api/.prettierignore new file mode 100644 index 0000000000..960a54081c --- /dev/null +++ b/packages/api/blocks-api/.prettierignore @@ -0,0 +1,2 @@ +lib/ +block-meta.json \ No newline at end of file diff --git a/packages/api/blocks-api/CHANGELOG.md b/packages/api/blocks-api/CHANGELOG.md index d31d3cd868..bcfdd6a54a 100644 --- a/packages/api/blocks-api/CHANGELOG.md +++ b/packages/api/blocks-api/CHANGELOG.md @@ -101,6 +101,8 @@ - Admin: `axios` - API: `@aws-sdk/client-s3`, `@azure/storage-blob` and `pg-error-constants` +## 6.16.0 + ## 6.15.1 ## 6.15.0 diff --git a/packages/api/blocks-api/lint-staged.config.js b/packages/api/blocks-api/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/api/blocks-api/lint-staged.config.js +++ b/packages/api/blocks-api/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/api/blocks-api/package.json b/packages/api/blocks-api/package.json index 06323017c0..55588658c2 100644 --- a/packages/api/blocks-api/package.json +++ b/packages/api/blocks-api/package.json @@ -18,8 +18,9 @@ "dev": "tsc --watch --preserveWatchOutput -p tsconfig.build.json", "generate-block-meta": "ts-node generate-block-meta.ts", "generate-block-meta:watch": "chokidar \"src/\" -c \"npm run generate-block-meta\"", - "lint": "run-p lint:eslint lint:tsc", + "lint": "run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit", "test": "jest --verbose=true", "test:watch": "jest -w" diff --git a/packages/api/cms-api/.prettierignore b/packages/api/cms-api/.prettierignore new file mode 100644 index 0000000000..49514d7075 --- /dev/null +++ b/packages/api/cms-api/.prettierignore @@ -0,0 +1,2 @@ +block-meta.json +lib/ \ No newline at end of file diff --git a/packages/api/cms-api/CHANGELOG.md b/packages/api/cms-api/CHANGELOG.md index 3542c19d89..7f5d0c32ff 100644 --- a/packages/api/cms-api/CHANGELOG.md +++ b/packages/api/cms-api/CHANGELOG.md @@ -357,6 +357,51 @@ - Updated dependencies [ebf597120] - @comet/blocks-api@7.0.0-beta.0 +## 6.16.0 + +### Minor Changes + +- 5e830f8d9: Add an [Azure AI Translator](https://azure.microsoft.com/en-us/products/ai-services/ai-translator) implementation of the content translation feature + + To use it, do the following: + + **API:** + + ```diff + // app.module.ts + export class AppModule { + static forRoot(config: Config): DynamicModule { + return { + imports: [ + // ... + + AzureAiTranslatorModule.register({ + + endpoint: envVars.AZURE_AI_TRANSLATOR_ENDPOINT, + + key: envVars.AZURE_AI_TRANSLATOR_KEY, + + region: envVars.AZURE_AI_TRANSLATOR_REGION, + + }), + ], + ``` + + Users need the `translation` permission to use the translation feature. + + **Admin:** + + Wrap the section where you want to use the content translation with the `AzureAiTranslatorProvider` provider: + + ```tsx + {/* ... */} + ``` + + Note: `AzureAiTranslatorProvider` automatically checks for the `translation` permission. The translation button is only shown for users with this permission. + +### Patch Changes + +- f7d405dfa: Fix the duplicate filename check in `FilesService#updateByEntity` + + Previously, we checked the existing file name (`entity.name`) for the check instead of the new name (`input.name`). This never resulted in an error. + + - @comet/blocks-api@6.16.0 + ## 6.15.1 ### Patch Changes diff --git a/packages/api/cms-api/generate-schema.ts b/packages/api/cms-api/generate-schema.ts index e323c91c18..e6f5f48d28 100644 --- a/packages/api/cms-api/generate-schema.ts +++ b/packages/api/cms-api/generate-schema.ts @@ -30,6 +30,7 @@ import { createFilesResolver } from "./src/dam/files/files.resolver"; import { createFoldersResolver } from "./src/dam/files/folders.resolver"; import { RedirectInputFactory } from "./src/redirects/dto/redirect-input.factory"; import { RedirectEntityFactory } from "./src/redirects/entities/redirect-entity.factory"; +import { AzureAiTranslatorResolver } from "./src/translation/azure-ai-translator.resolver"; import { UserResolver } from "./src/user-permissions/user.resolver"; import { UserContentScopesResolver } from "./src/user-permissions/user-content-scopes.resolver"; import { UserPermissionResolver } from "./src/user-permissions/user-permission.resolver"; @@ -96,6 +97,7 @@ async function generateSchema(): Promise { UserResolver, UserPermissionResolver, UserContentScopesResolver, + AzureAiTranslatorResolver, GenerateAltTextResolver, GenerateImageTitleResolver, ]); diff --git a/packages/api/cms-api/lint-staged.config.js b/packages/api/cms-api/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/api/cms-api/lint-staged.config.js +++ b/packages/api/cms-api/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/api/cms-api/package.json b/packages/api/cms-api/package.json index c802ab403c..ef32e4ff81 100644 --- a/packages/api/cms-api/package.json +++ b/packages/api/cms-api/package.json @@ -20,8 +20,9 @@ "clean": "rimraf lib", "build": "$npm_execpath run clean && tsc -p tsconfig.build.json", "dev": "tsc --watch --preserveWatchOutput -p tsconfig.build.json", - "lint": "run-p lint:eslint lint:tsc", + "lint": "run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit", "generate-schema": "ts-node generate-schema.ts", "generate-schema:watch": "chokidar \"src/\" -c \"$npm_execpath generate-schema\"", @@ -32,6 +33,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.591.0", + "@azure-rest/ai-translation-text": "^1.0.0-beta.1", "@azure/openai": "1.0.0-beta.11", "@azure/storage-blob": "^12.23.0", "@comet/blocks-api": "workspace:^7.0.0-beta.6", diff --git a/packages/api/cms-api/schema.gql b/packages/api/cms-api/schema.gql index 4fb60a75fa..69bd2f6857 100644 --- a/packages/api/cms-api/schema.gql +++ b/packages/api/cms-api/schema.gql @@ -365,6 +365,7 @@ type Query { userPermissionsAvailablePermissions: [String!]! userPermissionsContentScopes(userId: String!, skipManual: Boolean): [JSONObject!]! userPermissionsAvailableContentScopes: [JSONObject!]! + azureAiTranslate(input: AzureAiTranslationInput!): String! } input RedirectScopeInput { @@ -493,6 +494,11 @@ enum UserSortField { status } +input AzureAiTranslationInput { + text: String! + targetLanguage: String! +} + type Mutation { createBuilds(input: CreateBuildsInput!): Boolean! createRedirect(scope: RedirectScopeInput! = {}, input: RedirectInput!): Redirect! diff --git a/packages/api/cms-api/src/dam/common/decorators/has-valid-filename.decorator.ts b/packages/api/cms-api/src/dam/common/decorators/has-valid-filename.decorator.ts new file mode 100644 index 0000000000..d560da1732 --- /dev/null +++ b/packages/api/cms-api/src/dam/common/decorators/has-valid-filename.decorator.ts @@ -0,0 +1,69 @@ +import { InjectRepository } from "@mikro-orm/nestjs"; +import { EntityRepository } from "@mikro-orm/postgresql"; +import { Injectable } from "@nestjs/common"; +import { registerDecorator, ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator"; +import { basename, extname } from "path"; + +import { UpdateFileInput } from "../../files/dto/file.input"; +import { UpdateDamFileArgs } from "../../files/dto/update-dam-file.args"; +import { FILE_ENTITY, FileInterface } from "../../files/entities/file.entity"; +import { slugifyFilename } from "../../files/files.utils"; + +export const HasValidFilename = () => { + // eslint-disable-next-line @typescript-eslint/ban-types + return (object: Object, propertyName: string): void => { + registerDecorator({ + target: object.constructor, + propertyName, + validator: HasValidFilenameConstraint, + }); + }; +}; + +export interface HasValidFilenameValidationArguments extends ValidationArguments { + object: UpdateDamFileArgs; +} + +@ValidatorConstraint({ name: "HasValidFilename", async: true }) +@Injectable() +export class HasValidFilenameConstraint implements ValidatorConstraintInterface { + errorMessage: string | undefined; + + constructor(@InjectRepository(FILE_ENTITY) private readonly filesRepository: EntityRepository) {} + + async validate(value: UpdateFileInput, validationArguments: HasValidFilenameValidationArguments): Promise { + if (value.name === undefined) { + return true; + } + + const newFilename = value.name; + const newExtension = extname(newFilename); + const newBasename = basename(newFilename, newExtension); + + if (newExtension.length === 0) { + this.errorMessage = `Filename ${newFilename} has no extension`; + return false; + } + + if (newFilename !== slugifyFilename(newBasename, newExtension)) { + this.errorMessage = `Filename ${newFilename} contains invalid symbols`; + return false; + } + + const id = validationArguments.object.id; + const file = await this.filesRepository.findOneOrFail({ id }); + + const oldExtension = extname(file.name); + + if (newExtension !== oldExtension) { + this.errorMessage = `Extension cannot be changed. Previous extension: ${oldExtension}, new extension: ${newExtension}`; + return false; + } + + return true; + } + + defaultMessage(): string { + return this.errorMessage ?? "Invalid filename"; + } +} diff --git a/packages/api/cms-api/src/dam/dam.module.ts b/packages/api/cms-api/src/dam/dam.module.ts index 9d3532c724..269c032fca 100644 --- a/packages/api/cms-api/src/dam/dam.module.ts +++ b/packages/api/cms-api/src/dam/dam.module.ts @@ -8,6 +8,7 @@ import { PixelImageBlockTransformerService } from "./blocks/pixel-image-block-tr import { SvgImageBlockTransformerService } from "./blocks/svg-image-block-transformer.service"; import { DamVideoBlockTransformerService } from "./blocks/video/dam-video-block-transformer.service"; import { ScaledImagesCacheService } from "./cache/scaled-images-cache.service"; +import { HasValidFilenameConstraint } from "./common/decorators/has-valid-filename.decorator"; import { DamConfig } from "./dam.config"; import { DAM_CONFIG, DAM_FILE_VALIDATION_SERVICE, IMGPROXY_CONFIG } from "./dam.constants"; import { createDamItemsResolver } from "./files/dam-items.resolver"; @@ -130,6 +131,7 @@ export class DamModule { SvgImageBlockTransformerService, DamVideoBlockTransformerService, DamFileDownloadLinkBlockTransformerService, + HasValidFilenameConstraint, ], controllers: [createFilesController({ Scope }), FoldersController, ImagesController], exports: [ diff --git a/packages/api/cms-api/src/dam/files/dto/update-dam-file.args.ts b/packages/api/cms-api/src/dam/files/dto/update-dam-file.args.ts new file mode 100644 index 0000000000..8e555e6a9b --- /dev/null +++ b/packages/api/cms-api/src/dam/files/dto/update-dam-file.args.ts @@ -0,0 +1,19 @@ +import { ArgsType, Field, ID } from "@nestjs/graphql"; +import { Type } from "class-transformer"; +import { IsUUID, ValidateNested } from "class-validator"; + +import { HasValidFilename } from "../../common/decorators/has-valid-filename.decorator"; +import { UpdateFileInput } from "./file.input"; + +@ArgsType() +export class UpdateDamFileArgs { + @Field(() => ID) + @IsUUID() + id: string; + + @Field(() => UpdateFileInput) + @Type(() => UpdateFileInput) + @ValidateNested() + @HasValidFilename() + input: UpdateFileInput; +} diff --git a/packages/api/cms-api/src/dam/files/file-validation.service.ts b/packages/api/cms-api/src/dam/files/file-validation.service.ts index acb07c393e..70e7ce18b7 100644 --- a/packages/api/cms-api/src/dam/files/file-validation.service.ts +++ b/packages/api/cms-api/src/dam/files/file-validation.service.ts @@ -1,8 +1,7 @@ import { readFile } from "fs/promises"; -import * as mimedb from "mime-db"; import { FileUploadInput } from "./dto/file-upload.input"; -import { svgContainsJavaScript } from "./files.utils"; +import { getValidExtensionsForMimetype, svgContainsJavaScript } from "./files.utils"; export class FileValidationService { constructor(public config: { maxFileSize: number; acceptedMimeTypes: string[] }) {} @@ -34,17 +33,9 @@ export class FileValidationService { return `Invalid file name: Missing file extension`; } - let supportedExtensions: readonly string[] | undefined; - if (file.mimetype === "application/x-zip-compressed") { - // zip files in Windows, not supported by mime-db - // see https://github.com/jshttp/mime-db/issues/245 - supportedExtensions = ["zip"]; - } else { - supportedExtensions = mimedb[file.mimetype]?.extensions; - } - + const supportedExtensions = getValidExtensionsForMimetype(file.mimetype); if (supportedExtensions === undefined || !supportedExtensions.includes(extension)) { - return `File type and extension mismatch: .${extension} and ${file.mimetype} are incompatible`; + return `File type and extension mismatch: ${extension} and ${file.mimetype} are incompatible`; } return undefined; diff --git a/packages/api/cms-api/src/dam/files/files.resolver.ts b/packages/api/cms-api/src/dam/files/files.resolver.ts index 4d1290c447..12d4f74153 100644 --- a/packages/api/cms-api/src/dam/files/files.resolver.ts +++ b/packages/api/cms-api/src/dam/files/files.resolver.ts @@ -22,6 +22,7 @@ import { createFileArgs, FileArgsInterface, MoveDamFilesArgs } from "./dto/file. import { UpdateFileInput } from "./dto/file.input"; import { FilenameInput, FilenameResponse } from "./dto/filename.args"; import { createFindCopiesOfFileInScopeArgs, FindCopiesOfFileInScopeArgsInterface } from "./dto/find-copies-of-file-in-scope.args"; +import { UpdateDamFileArgs } from "./dto/update-dam-file.args"; import { FileInterface } from "./entities/file.entity"; import { FolderInterface } from "./entities/folder.entity"; import { FileUploadService } from "./file-upload.service"; @@ -94,10 +95,7 @@ export function createFilesResolver({ @Mutation(() => File) @AffectedEntity(File) - async updateDamFile( - @Args("id", { type: () => ID }) id: string, - @Args("input", { type: () => UpdateFileInput }) input: UpdateFileInput, - ): Promise { + async updateDamFile(@Args({ type: () => UpdateDamFileArgs }) { id, input }: UpdateDamFileArgs): Promise { return this.filesService.updateById(id, input); } diff --git a/packages/api/cms-api/src/dam/files/files.service.ts b/packages/api/cms-api/src/dam/files/files.service.ts index 4710bd7e8a..5fbab12c9d 100644 --- a/packages/api/cms-api/src/dam/files/files.service.ts +++ b/packages/api/cms-api/src/dam/files/files.service.ts @@ -34,6 +34,7 @@ import { FileUploadInput } from "./dto/file-upload.input"; import { FILE_TABLE_NAME, FileInterface } from "./entities/file.entity"; import { DamFileImage } from "./entities/file-image.entity"; import { FolderInterface } from "./entities/folder.entity"; +import { FileValidationService } from "./file-validation.service"; import { createHashedPath, slugifyFilename } from "./files.utils"; import { FoldersService } from "./folders.service"; @@ -116,6 +117,7 @@ export class FilesService { private readonly orm: MikroORM, private readonly contentScopeService: ContentScopeService, @Inject(ACCESS_CONTROL_SERVICE) private accessControlService: AccessControlServiceInterface, + private readonly fileValidationService: FileValidationService, ) {} private selectQueryBuilder(): QueryBuilder { @@ -251,10 +253,11 @@ export class FilesService { entity.image.cropArea = image.cropArea; } - const entityWithSameName = await this.findOneByFilenameAndFolder({ filename: entity.name, folderId }, entity.scope); - - if (entityWithSameName !== null && entityWithSameName.id !== entity.id) { - throw new Error(`Entity with name '${entity.name}' already exists in ${folder ? `folder '${folder.name}'` : "root folder"}`); + if (input.name) { + const entityWithSameName = await this.findOneByFilenameAndFolder({ filename: input.name, folderId }, entity.scope); + if (entityWithSameName !== null && entityWithSameName.id !== entity.id) { + throw new Error(`Entity with name '${input.name}' already exists in ${folder ? `folder '${folder.name}'` : "root folder"}`); + } } const file = Object.assign(entity, { diff --git a/packages/api/cms-api/src/dam/files/files.utils.ts b/packages/api/cms-api/src/dam/files/files.utils.ts index 26204f357e..a2b8aeae1c 100644 --- a/packages/api/cms-api/src/dam/files/files.utils.ts +++ b/packages/api/cms-api/src/dam/files/files.utils.ts @@ -1,12 +1,14 @@ import { XMLParser } from "fast-xml-parser"; import { unlink } from "fs/promises"; +import * as mimedb from "mime-db"; import { sep } from "path"; import slugify from "slugify"; import { FileUploadInput } from "./dto/file-upload.input"; export function slugifyFilename(filename: string, extension: string): string { - return `${slugify(filename, { locale: "de", lower: true, strict: true })}${extension}`; + const extensionWithDot = extension.startsWith(".") ? extension : `.${extension}`; + return `${slugify(filename)}${extensionWithDot}`; } export const createHashedPath = (contentHash: string): string => [contentHash.substr(0, 2), contentHash.substr(2, 2), contentHash].join(sep); @@ -79,3 +81,16 @@ export const removeMulterTempFile = async (file: FileUploadInput) => { await unlink(path); }; + +export const getValidExtensionsForMimetype = (mimetype: string) => { + let supportedExtensions: readonly string[] | undefined; + if (mimetype === "application/x-zip-compressed") { + // zip files in Windows, not supported by mime-db + // see https://github.com/jshttp/mime-db/issues/245 + supportedExtensions = ["zip"]; + } else { + supportedExtensions = mimedb[mimetype]?.extensions; + } + + return supportedExtensions; +}; diff --git a/packages/api/cms-api/src/index.ts b/packages/api/cms-api/src/index.ts index c7a8ac3e26..d23a148190 100644 --- a/packages/api/cms-api/src/index.ts +++ b/packages/api/cms-api/src/index.ts @@ -160,6 +160,7 @@ export { RedirectsModule } from "./redirects/redirects.module"; export { createRedirectsResolver } from "./redirects/redirects.resolver"; export { RedirectsService } from "./redirects/redirects.service"; export { IsValidRedirectSource, IsValidRedirectSourceConstraint } from "./redirects/validators/isValidRedirectSource"; +export { AzureAiTranslatorModule } from "./translation/azure-ai-translator.module"; export { AbstractAccessControlService } from "./user-permissions/access-control.service"; export { AffectedEntity, AffectedEntityMeta, AffectedEntityOptions } from "./user-permissions/decorators/affected-entity.decorator"; export { RequiredPermission } from "./user-permissions/decorators/required-permission.decorator"; diff --git a/packages/api/cms-api/src/mikro-orm/migrations/Migration20240702123233.ts b/packages/api/cms-api/src/mikro-orm/migrations/Migration20240702123233.ts new file mode 100644 index 0000000000..f3afa528e0 --- /dev/null +++ b/packages/api/cms-api/src/mikro-orm/migrations/Migration20240702123233.ts @@ -0,0 +1,57 @@ +import { Migration } from "@mikro-orm/migrations"; +import * as mimedb from "mime-db"; + +const getValidExtensionsForMimetype = (mimetype: string) => { + let supportedExtensions: readonly string[] | undefined; + if (mimetype === "application/x-zip-compressed") { + // zip files in Windows, not supported by mime-db + // see https://github.com/jshttp/mime-db/issues/245 + supportedExtensions = ["zip"]; + } else { + supportedExtensions = mimedb[mimetype]?.extensions; + } + + return supportedExtensions; +}; + +export class Migration20240702123233 extends Migration { + async up(): Promise { + // This migration adds a valid file extension to every DamFile#name that doesn't have an extension yet + + // Get all mimetypes in use + const mimetypes = (await this.execute('select distinct "DamFile".mimetype from "DamFile"')) as Array<{ mimetype: string }>; + + // Create a mapping of mimetypes to their valid extensions + const mimetypeToExtensionsMap: { [key: string]: string[] } = mimetypes.reduce((prev, m) => { + const mimetype = m.mimetype; + const extensions = getValidExtensionsForMimetype(mimetype); + return { ...prev, [mimetype]: extensions }; + }, {}); + + // Generate an input string for jsonb_build_object that maps mimetypes to their first valid extension + const sqlMimetypeToSingleExtensionMap = Object.entries(mimetypeToExtensionsMap) + .map(([mimetype, extensions]) => `'${mimetype}', '${extensions[0]}'`) + .join(", "); + + // Generate SQL clauses to check if the name doesn't end with a valid extension for each mimetype + const sqlCaseClauses = Object.entries(mimetypeToExtensionsMap) + .map(([mimetype, extensions]) => { + const conditions = extensions.map((ext) => `name NOT LIKE '%.${ext}'`).join(" AND "); + return `(mimetype = '${mimetype}' AND (${conditions}))`; + }) + .join(" OR "); + + if (sqlCaseClauses.length > 0) { + this.addSql(` + WITH mimetype_extension_map AS ( + SELECT jsonb_build_object(${sqlMimetypeToSingleExtensionMap}) AS map + ) + + UPDATE "DamFile" + SET "name" = CONCAT("name", '.', mimetype_extension_map.map ->> "DamFile".mimetype) + FROM mimetype_extension_map + WHERE ${sqlCaseClauses}; + `); + } + } +} diff --git a/packages/api/cms-api/src/mikro-orm/mikro-orm.module.ts b/packages/api/cms-api/src/mikro-orm/mikro-orm.module.ts index 2d77176624..38809eca2d 100644 --- a/packages/api/cms-api/src/mikro-orm/mikro-orm.module.ts +++ b/packages/api/cms-api/src/mikro-orm/mikro-orm.module.ts @@ -23,6 +23,7 @@ import { Migration20231206123505 } from "./migrations/Migration20231206123505"; import { Migration20231215103630 } from "./migrations/Migration20231215103630"; import { Migration20231218092313 } from "./migrations/Migration20231218092313"; import { Migration20231222090009 } from "./migrations/Migration20231222090009"; +import { Migration20240702123233 } from "./migrations/Migration20240702123233"; export const PG_UNIQUE_CONSTRAINT_VIOLATION = "23505"; @@ -83,6 +84,7 @@ export function createOrmConfig({ migrations, ...defaults }: MikroOrmNestjsOptio { name: "Migration20231222090009", class: Migration20231222090009 }, { name: "Migration20231204140305", class: Migration20231204140305 }, { name: "Migration20231218092313", class: Migration20231218092313 }, + { name: "Migration20240702123233", class: Migration20240702123233 }, ...(migrations?.migrationsList || []), ].sort((migrationA, migrationB) => { if (migrationA.name < migrationB.name) { diff --git a/packages/api/cms-api/src/translation/azure-ai-translator.config.ts b/packages/api/cms-api/src/translation/azure-ai-translator.config.ts new file mode 100644 index 0000000000..51cfe532a0 --- /dev/null +++ b/packages/api/cms-api/src/translation/azure-ai-translator.config.ts @@ -0,0 +1,5 @@ +export interface AzureAiTranslatorConfig { + endpoint: string; + key: string; + region: string; +} diff --git a/packages/api/cms-api/src/translation/azure-ai-translator.constants.ts b/packages/api/cms-api/src/translation/azure-ai-translator.constants.ts new file mode 100644 index 0000000000..3fe85ecf19 --- /dev/null +++ b/packages/api/cms-api/src/translation/azure-ai-translator.constants.ts @@ -0,0 +1 @@ +export const AZURE_AI_TRANSLATOR_CONFIG = "azure-ai-translator-config"; diff --git a/packages/api/cms-api/src/translation/azure-ai-translator.module.ts b/packages/api/cms-api/src/translation/azure-ai-translator.module.ts new file mode 100644 index 0000000000..38723ad66e --- /dev/null +++ b/packages/api/cms-api/src/translation/azure-ai-translator.module.ts @@ -0,0 +1,20 @@ +import { DynamicModule, Module, ValueProvider } from "@nestjs/common"; + +import { AzureAiTranslatorConfig } from "./azure-ai-translator.config"; +import { AZURE_AI_TRANSLATOR_CONFIG } from "./azure-ai-translator.constants"; +import { AzureAiTranslatorResolver } from "./azure-ai-translator.resolver"; + +@Module({}) +export class AzureAiTranslatorModule { + static register(config: AzureAiTranslatorConfig): DynamicModule { + const configProvider: ValueProvider = { + provide: AZURE_AI_TRANSLATOR_CONFIG, + useValue: config, + }; + + return { + module: AzureAiTranslatorModule, + providers: [configProvider, AzureAiTranslatorResolver], + }; + } +} diff --git a/packages/api/cms-api/src/translation/azure-ai-translator.resolver.ts b/packages/api/cms-api/src/translation/azure-ai-translator.resolver.ts new file mode 100644 index 0000000000..74630f6b8c --- /dev/null +++ b/packages/api/cms-api/src/translation/azure-ai-translator.resolver.ts @@ -0,0 +1,48 @@ +import createClient, { isUnexpected, TextTranslationClient } from "@azure-rest/ai-translation-text"; +import { Inject } from "@nestjs/common"; +import { Args, Query, Resolver } from "@nestjs/graphql"; + +import { RequiredPermission } from "../user-permissions/decorators/required-permission.decorator"; +import { AzureAiTranslatorConfig } from "./azure-ai-translator.config"; +import { AZURE_AI_TRANSLATOR_CONFIG } from "./azure-ai-translator.constants"; +import { AzureAiTranslationInput } from "./dto/azure-ai-translation.input"; + +@Resolver() +@RequiredPermission(["translation"], { skipScopeCheck: true }) +export class AzureAiTranslatorResolver { + private readonly translationClient: TextTranslationClient; + + constructor(@Inject(AZURE_AI_TRANSLATOR_CONFIG) private readonly config: AzureAiTranslatorConfig) { + this.translationClient = createClient(config.endpoint, { + key: config.key, + region: config.region, + }); + } + + @Query(() => String) + async azureAiTranslate(@Args("input") input: AzureAiTranslationInput): Promise { + const translateResponse = await this.translationClient.path("/translate").post({ + body: [{ text: input.text }], + queryParameters: { + to: input.targetLanguage, + textType: "html", + }, + }); + + if (isUnexpected(translateResponse)) { + // https://learn.microsoft.com/en-us/azure/ai-services/translator/reference/v3-0-reference#errors + if (translateResponse.body.error.code === 403001) { + throw new Error("Translation failed. Exceeded free quota."); + } else if (translateResponse.status === "429") { + throw new Error("Translation failed. Exceeded request limit."); + } + + throw new Error( + `Translation failed. Status: ${JSON.stringify(translateResponse.status)} Response Body: ${JSON.stringify(translateResponse.body)}`, + ); + } + + const result = translateResponse.body[0].translations[0].text; + return result; + } +} diff --git a/packages/api/cms-api/src/translation/dto/azure-ai-translation.input.ts b/packages/api/cms-api/src/translation/dto/azure-ai-translation.input.ts new file mode 100644 index 0000000000..393ac0fc46 --- /dev/null +++ b/packages/api/cms-api/src/translation/dto/azure-ai-translation.input.ts @@ -0,0 +1,15 @@ +import { Field, InputType } from "@nestjs/graphql"; +import { IsNotEmpty, IsString } from "class-validator"; + +@InputType() +export class AzureAiTranslationInput { + @IsNotEmpty() + @IsString() + @Field() + text: string; + + @IsNotEmpty() + @IsString() + @Field() + targetLanguage: string; +} diff --git a/packages/cli/.prettierignore b/packages/cli/.prettierignore new file mode 100644 index 0000000000..f1ff06d608 --- /dev/null +++ b/packages/cli/.prettierignore @@ -0,0 +1 @@ +lib/ \ No newline at end of file diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index ae3570082d..3c3dcf05ee 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -14,6 +14,8 @@ ## 7.0.0-beta.0 +## 6.16.0 + ## 6.15.1 ## 6.15.0 diff --git a/packages/cli/lint-staged.config.js b/packages/cli/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/cli/lint-staged.config.js +++ b/packages/cli/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/cli/package.json b/packages/cli/package.json index 795e08821c..2a3dc4bca5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -20,8 +20,9 @@ "build": "$npm_execpath run clean && tsc", "clean": "rimraf lib", "dev": "tsc --watch", - "lint": "run-p lint:eslint lint:tsc", + "lint": "run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc" }, "dependencies": { diff --git a/packages/eslint-config/.eslintrc.json b/packages/eslint-config/.eslintrc.json new file mode 100644 index 0000000000..514ae3d75a --- /dev/null +++ b/packages/eslint-config/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "plugins": ["json-files"], + "rules": { + "json-files/sort-package-json": "error" + } +} diff --git a/packages/eslint-config/CHANGELOG.md b/packages/eslint-config/CHANGELOG.md index 7570a80cec..af6db6fe9e 100644 --- a/packages/eslint-config/CHANGELOG.md +++ b/packages/eslint-config/CHANGELOG.md @@ -116,6 +116,12 @@ - @comet/eslint-plugin@7.0.0-beta.0 +## 6.16.0 + +### Patch Changes + +- @comet/eslint-plugin@6.16.0 + ## 6.15.1 ### Patch Changes diff --git a/packages/eslint-config/core.js b/packages/eslint-config/core.js index 96de759fc1..4e637cc71e 100644 --- a/packages/eslint-config/core.js +++ b/packages/eslint-config/core.js @@ -24,7 +24,7 @@ module.exports = { rules: { "@typescript-eslint/no-unused-vars": ["error", { args: "none", ignoreRestSiblings: true }], "@typescript-eslint/no-inferrable-types": ["error", { ignoreProperties: true }], - "@typescript-eslint/prefer-enum-initializers": "error" + "@typescript-eslint/prefer-enum-initializers": "error", }, }, ], diff --git a/packages/eslint-config/lint-staged.config.js b/packages/eslint-config/lint-staged.config.js new file mode 100644 index 0000000000..c44a9dfe4a --- /dev/null +++ b/packages/eslint-config/lint-staged.config.js @@ -0,0 +1,4 @@ +module.exports = { + "*.{js,json,css,scss,md}": () => "pnpm lint:eslint", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", +}; diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 2791f84621..cab3665dec 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -8,8 +8,14 @@ }, "license": "BSD-2-Clause", "main": "core.js", + "scripts": { + "lint": "run-p lint:prettier lint:eslint", + "lint:eslint": "eslint --max-warnings 0 package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'" + }, "dependencies": { "@calm/eslint-plugin-react-intl": "^1.4.1", + "@comet/eslint-plugin": "workspace:^7.0.0-beta.6", "@next/eslint-plugin-next": "^12.0.0", "@typescript-eslint/eslint-plugin": "^5.48.2", "@typescript-eslint/parser": "^5.48.2", @@ -24,7 +30,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-simple-import-sort": "^9.0.0", "eslint-plugin-unused-imports": "^2.0.0", - "@comet/eslint-plugin": "workspace:^7.0.0-beta.6" + "npm-run-all": "^4.1.5" }, "devDependencies": { "eslint": "^8.32.0", diff --git a/packages/eslint-plugin/.eslintrc.json b/packages/eslint-plugin/.eslintrc.json index 7357859f57..f8f4de15ad 100644 --- a/packages/eslint-plugin/.eslintrc.json +++ b/packages/eslint-plugin/.eslintrc.json @@ -22,4 +22,3 @@ ], "ignorePatterns": ["bin/", "lib/**"] } - diff --git a/packages/eslint-plugin/.prettierignore b/packages/eslint-plugin/.prettierignore new file mode 100644 index 0000000000..f1ff06d608 --- /dev/null +++ b/packages/eslint-plugin/.prettierignore @@ -0,0 +1 @@ +lib/ \ No newline at end of file diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 9748b230bb..0e9ded0b4c 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -14,6 +14,8 @@ ## 7.0.0-beta.0 +## 6.16.0 + ## 6.15.1 ## 6.15.0 diff --git a/packages/eslint-plugin/jest.config.js b/packages/eslint-plugin/jest.config.js index d62b6e6f17..d6b15e6c42 100644 --- a/packages/eslint-plugin/jest.config.js +++ b/packages/eslint-plugin/jest.config.js @@ -2,4 +2,4 @@ module.exports = { testEnvironment: "node", transform: { "^.+\\.tsx?$": "ts-jest" }, rootDir: "./src", -}; \ No newline at end of file +}; diff --git a/packages/eslint-plugin/lint-staged.config.js b/packages/eslint-plugin/lint-staged.config.js new file mode 100644 index 0000000000..79e0324fe1 --- /dev/null +++ b/packages/eslint-plugin/lint-staged.config.js @@ -0,0 +1,5 @@ +module.exports = { + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", +}; diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 7cf79bde8f..c3a192440a 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -6,8 +6,9 @@ "build": "$npm_execpath run clean && tsc", "clean": "rimraf lib", "dev": "tsc --watch", - "lint": "run-p lint:eslint lint:tsc", - "lint:eslint": "eslint --max-warnings 0 src/", + "lint": "run-p lint:prettier lint:eslint lint:tsc", + "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc", "test": "jest", "test:watch": "jest --watch" diff --git a/packages/site/cms-site/.prettierignore b/packages/site/cms-site/.prettierignore new file mode 100644 index 0000000000..960a54081c --- /dev/null +++ b/packages/site/cms-site/.prettierignore @@ -0,0 +1,2 @@ +lib/ +block-meta.json \ No newline at end of file diff --git a/packages/site/cms-site/CHANGELOG.md b/packages/site/cms-site/CHANGELOG.md index cb9f12f856..7dea690e5a 100644 --- a/packages/site/cms-site/CHANGELOG.md +++ b/packages/site/cms-site/CHANGELOG.md @@ -224,6 +224,8 @@ - The `SitesConfig` must provide a `sitePreviewApiUrl` +## 6.16.0 + ## 6.15.1 ## 6.15.0 diff --git a/packages/site/cms-site/lint-staged.config.js b/packages/site/cms-site/lint-staged.config.js index ded5de27f0..79e0324fe1 100644 --- a/packages/site/cms-site/lint-staged.config.js +++ b/packages/site/cms-site/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { - "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", - "*.{ts,tsx}": () => "pnpm lint:tsc", + "src/**/*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", + "src/**/*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/packages/site/cms-site/package.json b/packages/site/cms-site/package.json index 2cbe5cc966..fc6bef0c38 100644 --- a/packages/site/cms-site/package.json +++ b/packages/site/cms-site/package.json @@ -18,8 +18,9 @@ "dev": "$npm_execpath generate-block-types && tsc --watch --preserveWatchOutput --project tsconfig.build.json", "generate-block-types": "comet generate-block-types", "generate-block-types:watch": "chokidar -s \"**/block-meta.json\" -c \"$npm_execpath generate-block-types\"", - "lint": "$npm_execpath generate-block-types && run-p lint:eslint lint:tsc", + "lint": "$npm_execpath generate-block-types && run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 --ext .ts,.tsx,.js,.jsx,.json,.md src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit", "test": "jest --verbose=true --passWithNoTests", "test:watch": "jest --watch" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5f09a3ba4..c670619fcb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1051,6 +1051,9 @@ importers: '@types/react-dom': specifier: ^17.0.17 version: 17.0.18 + npm-run-all: + specifier: ^4.1.5 + version: 4.1.5 prettier: specifier: ^2.6.2 version: 2.8.3 @@ -2245,6 +2248,9 @@ importers: '@aws-sdk/client-s3': specifier: ^3.591.0 version: 3.591.0 + '@azure-rest/ai-translation-text': + specifier: ^1.0.0-beta.1 + version: 1.0.0-beta.1 '@azure/openai': specifier: 1.0.0-beta.11 version: 1.0.0-beta.11 @@ -2595,6 +2601,9 @@ importers: eslint-plugin-unused-imports: specifier: ^2.0.0 version: 2.0.0(@typescript-eslint/eslint-plugin@5.49.0)(eslint@8.32.0) + npm-run-all: + specifier: ^4.1.5 + version: 4.1.5 devDependencies: eslint: specifier: ^8.32.0 @@ -4012,6 +4021,19 @@ packages: tslib: 2.6.3 dev: false + /@azure-rest/ai-translation-text@1.0.0-beta.1: + resolution: {integrity: sha512-h1xDrmVRbk6eAAqTHxy9Npv543cWteqgop15sVXBQhadOwzHoREn+UqMCzNfvL6/AjiInUlwNSaNQK1ANgobLA==} + engines: {node: '>=14.0.0'} + dependencies: + '@azure-rest/core-client': 1.4.0 + '@azure/core-auth': 1.7.2 + '@azure/core-rest-pipeline': 1.15.2 + '@azure/logger': 1.1.2 + tslib: 2.4.1 + transitivePeerDependencies: + - supports-color + dev: false + /@azure-rest/core-client@1.4.0: resolution: {integrity: sha512-ozTDPBVUDR5eOnMIwhggbnVmOrka4fXCs8n8mvUo4WLLc38kki6bAOByDoVZZPz/pZy2jMt2kwfpvy/UjALj6w==} engines: {node: '>=18.0.0'} @@ -4092,6 +4114,22 @@ packages: tslib: 2.6.3 dev: false + /@azure/core-rest-pipeline@1.15.2: + resolution: {integrity: sha512-BmWfpjc/QXc2ipHOh6LbUzp3ONCaa6xzIssTU0DwH9bbYNXJlGUL6tujx5TrbVd/QQknmS+vlQJGrCq2oL1gZA==} + engines: {node: '>=18.0.0'} + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.7.2 + '@azure/core-tracing': 1.1.2 + '@azure/core-util': 1.9.0 + '@azure/logger': 1.1.2 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.4 + tslib: 2.6.2 + transitivePeerDependencies: + - supports-color + dev: false + /@azure/core-rest-pipeline@1.16.0: resolution: {integrity: sha512-CeuTvsXxCUmEuxH5g/aceuSl6w2EugvNHKAtKKVdiX915EjJJxAwfzNNWZreNnbxHZ2fi0zaM6wwS23x2JVqSQ==} engines: {node: '>=18.0.0'} @@ -6273,7 +6311,7 @@ packages: babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.22.11) babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.22.11) babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.22.11) - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: false @@ -25310,7 +25348,6 @@ packages: parse-json: 4.0.0 pify: 3.0.0 strip-bom: 3.0.0 - dev: true /load-yaml-file@0.2.0: resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} @@ -25704,7 +25741,7 @@ packages: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} dependencies: - semver: 6.3.0 + semver: 6.3.1 /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -25874,7 +25911,6 @@ packages: /memorystream@0.3.1: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} - dev: true /meow@3.7.0: resolution: {integrity: sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==} @@ -26636,7 +26672,6 @@ packages: read-pkg: 3.0.0 shell-quote: 1.7.4 string.prototype.padend: 3.1.4 - dev: true /npm-run-path@2.0.2: resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} @@ -27221,7 +27256,6 @@ packages: dependencies: error-ex: 1.3.2 json-parse-better-errors: 1.0.2 - dev: true /parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} @@ -27524,7 +27558,6 @@ packages: resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==} engines: {node: '>=0.10'} hasBin: true - dev: true /pidtree@0.5.0: resolution: {integrity: sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==} @@ -29635,7 +29668,6 @@ packages: load-json-file: 4.0.0 normalize-package-data: 2.5.0 path-type: 3.0.0 - dev: true /read-pkg@5.2.0: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} @@ -32377,6 +32409,10 @@ packages: /tslib@2.5.0: resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: false + /tslib@2.6.3: resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} diff --git a/storybook/.prettierignore b/storybook/.prettierignore new file mode 100644 index 0000000000..e69ce802bb --- /dev/null +++ b/storybook/.prettierignore @@ -0,0 +1 @@ +public/mockServiceWorker.js \ No newline at end of file diff --git a/storybook/lint-staged.config.js b/storybook/lint-staged.config.js index ded5de27f0..f34ef2ec57 100644 --- a/storybook/lint-staged.config.js +++ b/storybook/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { "*.{ts,tsx,js,jsx,json,css,scss,md}": () => "pnpm lint:eslint", "*.{ts,tsx}": () => "pnpm lint:tsc", + "*.{js,json,md,yml,yaml}": () => "pnpm lint:prettier", }; diff --git a/storybook/package.json b/storybook/package.json index 62c835ec27..855dd0197b 100644 --- a/storybook/package.json +++ b/storybook/package.json @@ -9,8 +9,9 @@ "scripts": { "build-storybook": "build-storybook", "storybook": "start-storybook -p 26638 --no-version-updates", - "lint": "run-p lint:eslint lint:tsc", + "lint": "run-p lint:prettier lint:eslint lint:tsc", "lint:eslint": "eslint --max-warnings 0 src/ package.json", + "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'", "lint:tsc": "tsc --noEmit" }, "dependencies": { diff --git a/storybook/src/admin/form/AllFieldComponents.tsx b/storybook/src/admin/form/AllFieldComponents.tsx index c70f43a075..7e9967300b 100644 --- a/storybook/src/admin/form/AllFieldComponents.tsx +++ b/storybook/src/admin/form/AllFieldComponents.tsx @@ -11,6 +11,7 @@ import { FinalFormSearchTextField, FinalFormSelect, FinalFormSwitch, + NumberField, SearchField, SelectField, SwitchField, @@ -58,6 +59,7 @@ function Story() { ))} + { - alert(JSON.stringify(changes)); - }} - orderColumn="order" // if anything but 'pos' is used - > - {({ tableLocalChangesApi, data: changedData }) => ( - <> - { - // alternative to submit button - // tableLocalChangesApi.submitLocalDataChanges(); - }} - columns={[ - { - name: "task", - header: "Task", - }, - ]} - /> - - - )} - + {({ tableLocalChangesApi, data: changedData }) => ( + <> + { + // alternative to submit button + // tableLocalChangesApi.submitLocalDataChanges(); + }} + columns={[ + { + name: "task", + header: "Task", + }, + ]} + /> + + {/* Link to test router prompt */} + + To page 2 + + + )} + + + Page 2 + + ); } storiesOf("@comet/admin/table", module) + .addDecorator(storyRouterDecorator()) .addDecorator(dndProviderDecorator()) .add("DnD Order", () => ); diff --git a/storybook/src/admin/table/TableEditDialog.tsx b/storybook/src/admin/table/TableEditDialog.tsx deleted file mode 100644 index 2fec84c231..0000000000 --- a/storybook/src/admin/table/TableEditDialog.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { - EditDialog, - Field, - FinalForm, - FinalFormInput, - IEditDialogApi, - MainContent, - Selected, - Table, - Toolbar, - ToolbarActions, - ToolbarFillSpace, - ToolbarItem, -} from "@comet/admin"; -import { Add as AddIcon, Edit as EditIcon } from "@comet/admin-icons"; -import { Button, IconButton, Typography } from "@mui/material"; -import { storiesOf } from "@storybook/react"; -import * as React from "react"; - -import { apolloRestStoryDecorator } from "../../apollo-rest-story.decorator"; -import { storyRouterDecorator } from "../../story-router.decorator"; - -function Story() { - interface IEditFormProps { - row: IExampleRow; - mode: "edit" | "add"; - } - // Defined in story to be able to call setData, hence using useMemo to avoid multiple rendering - const EditForm = React.useMemo(() => { - return (props: IEditFormProps) => ( - { - await new Promise((resolve) => setTimeout(resolve, 500)); - setData((data) => { - const index = data.findIndex((d) => d.id === values.id); - data[index] = values; - return [...data]; - }); - }} - > - - - ); - }, []); - - interface IExampleRow { - id: number; - foo: string; - bar: string; - } - const [data, setData] = React.useState([ - { id: 1, foo: "blub", bar: "blub" }, - { id: 2, foo: "blub", bar: "blub" }, - ]); - const editDialog = React.useRef(null); - return ( - <> - - - Edit Dialog - - - - } - onClick={(ev) => { - editDialog.current?.openAddDialog(); - }} - > - Add - - - - - ( - { - editDialog.current?.openEditDialog(String(row.id)); - }} - size="large" - > - - - ), - }, - ]} - /> - - - {({ selectedId, selectionMode }) => ( - <> - {selectionMode && ( - - {(row, { selectionMode: sm }) => { - if (row === undefined) { - return null; - } - - return ; - }} - - )} - - )} - - - - ); -} - -storiesOf("@comet/admin/table", module) - .addDecorator(storyRouterDecorator()) - .addDecorator(apolloRestStoryDecorator()) - .add("EditDialog", () => ); diff --git a/storybook/src/admin/table/TableEditDialogHooks.tsx b/storybook/src/admin/table/TableEditDialogHooks.tsx index 8b64a61e91..3e958c816d 100644 --- a/storybook/src/admin/table/TableEditDialogHooks.tsx +++ b/storybook/src/admin/table/TableEditDialogHooks.tsx @@ -21,7 +21,7 @@ import { storyRouterDecorator } from "../../story-router.decorator"; function Story() { interface IEditFormProps { - row: IExampleRow; + row?: IExampleRow; mode: "edit" | "add"; } // Defined in story to be able to call setData, hence using useMemo to avoid multiple rendering @@ -31,12 +31,20 @@ function Story() { mode={props.mode} initialValues={props.row} onSubmit={async (values: IExampleRow) => { - await new Promise((resolve) => setTimeout(resolve, 500)); - setData((data) => { - const index = data.findIndex((d) => d.id === values.id); - data[index] = values; - return [...data]; - }); + if (props.mode == "edit") { + await new Promise((resolve) => setTimeout(resolve, 500)); + setData((data) => { + const index = data.findIndex((d) => d.id === values.id); + data[index] = values; + return [...data]; + }); + } else { + setData((data) => { + const lastId = data.length; + const newVal: IExampleRow = { id: lastId + 1, foo: values.foo, bar: values.foo }; + return [...data, newVal]; + }); + } }} > @@ -109,10 +117,6 @@ function Story() { {selection.mode && ( {(row, { selectionMode: sm }) => { - if (row === undefined) { - return null; - } - return ; }} diff --git a/storybook/src/admin/table/TableStackEditDialog.tsx b/storybook/src/admin/table/TableStackEditDialogHooks.tsx similarity index 79% rename from storybook/src/admin/table/TableStackEditDialog.tsx rename to storybook/src/admin/table/TableStackEditDialogHooks.tsx index c95f50ba95..146214618f 100644 --- a/storybook/src/admin/table/TableStackEditDialog.tsx +++ b/storybook/src/admin/table/TableStackEditDialogHooks.tsx @@ -1,9 +1,7 @@ import { - EditDialog, Field, FinalForm, FinalFormInput, - IEditDialogApi, MainContent, Selected, Stack, @@ -14,6 +12,7 @@ import { ToolbarActions, ToolbarFillSpace, ToolbarItem, + useEditDialog, } from "@comet/admin"; import { Add as AddIcon, Edit as EditIcon } from "@comet/admin-icons"; import { Button, IconButton, Typography } from "@mui/material"; @@ -30,7 +29,7 @@ interface IExampleRow { } interface IEditFormProps { - row: IExampleRow; + row?: IExampleRow; mode: "edit" | "add"; } function EditForm(props: IEditFormProps) { @@ -53,7 +52,7 @@ function Story() { { id: 2, foo: "blub", bar: "blub" }, ]; - const editDialog = React.useRef(null); + const [EditDialog, selection, api] = useEditDialog(); return ( <> @@ -71,7 +70,7 @@ function Story() { variant="contained" startIcon={} onClick={(ev) => { - editDialog.current?.openAddDialog(); + api.openAddDialog(); }} > Add @@ -98,7 +97,7 @@ function Story() { render: (row) => ( { - editDialog.current?.openEditDialog(String(row.id)); + api.openEditDialog(String(row.id)); }} size="large" > @@ -116,21 +115,13 @@ function Story() { - - {({ selectedId, selectionMode }) => ( - <> - {selectionMode && ( - - {(row, { selectionMode: sm }) => { - if (row === undefined) { - return null; - } - - return ; - }} - - )} - + + {selection.mode && ( + + {(row, { selectionMode: sm }) => { + return ; + }} + )}

This story uses a Stack plus an EditDialog

@@ -141,4 +132,4 @@ function Story() { storiesOf("@comet/admin/table", module) .addDecorator(storyRouterDecorator()) .addDecorator(apolloRestStoryDecorator()) - .add("Stack+EditDialog", () => ); + .add("Stack + EditDialog Hooks", () => ); diff --git a/packages/admin/admin-stories/src/admin/tooltip/IconWithTooltip.tsx b/storybook/src/admin/tooltip/IconWithTooltip.tsx similarity index 68% rename from packages/admin/admin-stories/src/admin/tooltip/IconWithTooltip.tsx rename to storybook/src/admin/tooltip/IconWithTooltip.tsx index f124863eda..7deec9a804 100644 --- a/packages/admin/admin-stories/src/admin/tooltip/IconWithTooltip.tsx +++ b/storybook/src/admin/tooltip/IconWithTooltip.tsx @@ -5,10 +5,8 @@ import * as React from "react"; storiesOf("@comet/admin", module).add("Icon with Tooltip", () => { return ( - <> - - - - + + + ); }); diff --git a/storybook/src/docs/form/components/NumberField.stories.mdx b/storybook/src/docs/form/components/NumberField.stories.mdx new file mode 100644 index 0000000000..f1494193c8 --- /dev/null +++ b/storybook/src/docs/form/components/NumberField.stories.mdx @@ -0,0 +1,27 @@ +import { Meta, Canvas, Story, Source } from "@storybook/addon-docs"; + + + +# Number Field + +The NumberField can be used for number inputs. It automatically formats the numbers with thousand-separators and decimal symbols in the current locale. You can also use it for currency inputs by passing the currency symbol as `startAdornment` or `endAdornment`. The value is stored as type `number` in the form values. + +## Basic usage + + + + + +## Usage with decimals + +The decimals prop is used to define, how many decimal digits the value should have. + + + + + +## Usage as Currency Field + + + + diff --git a/storybook/src/docs/form/components/stories/NumberField.stories.tsx b/storybook/src/docs/form/components/stories/NumberField.stories.tsx new file mode 100644 index 0000000000..4734f82499 --- /dev/null +++ b/storybook/src/docs/form/components/stories/NumberField.stories.tsx @@ -0,0 +1,34 @@ +import { FinalForm, NumberField } from "@comet/admin"; +import { storiesOf } from "@storybook/react"; +import * as React from "react"; + +import { apolloRestStoryDecorator } from "../../../../apollo-rest-story.decorator"; + +storiesOf("stories/Form/Components/NumberField", module) + .addDecorator(apolloRestStoryDecorator()) + .add("NumberField", () => { + return ( + {}}> + + + ); + }); +storiesOf("stories/Form/Components/NumberField", module) + .addDecorator(apolloRestStoryDecorator()) + .add("WithDecimals", () => { + return ( + {}}> + + + ); + }); +storiesOf("stories/Form/Components/NumberField", module) + .addDecorator(apolloRestStoryDecorator()) + .add("Currency", () => { + return ( + {}}> + + + + ); + });