From a4fcdeb517214253225256b9ee66f1f7ad65a415 Mon Sep 17 00:00:00 2001 From: Kerstin Reichinger <70954479+kerstin97@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:19:01 +0100 Subject: [PATCH 001/266] Enable vertical resizing for multiline InputBase (#2845) Co-authored-by: Kerstin Reichinger --- .changeset/fluffy-jars-search.md | 5 +++++ .../admin/admin-theme/src/componentsTheme/MuiInputBase.ts | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/fluffy-jars-search.md diff --git a/.changeset/fluffy-jars-search.md b/.changeset/fluffy-jars-search.md new file mode 100644 index 0000000000..9547b7ee25 --- /dev/null +++ b/.changeset/fluffy-jars-search.md @@ -0,0 +1,5 @@ +--- +"@comet/admin-theme": minor +--- + +Enable vertical resizing for `TextAreaField` and other multiline inputs diff --git a/packages/admin/admin-theme/src/componentsTheme/MuiInputBase.ts b/packages/admin/admin-theme/src/componentsTheme/MuiInputBase.ts index c599b30e3a..9e4ee9632f 100644 --- a/packages/admin/admin-theme/src/componentsTheme/MuiInputBase.ts +++ b/packages/admin/admin-theme/src/componentsTheme/MuiInputBase.ts @@ -42,6 +42,7 @@ export const getMuiInputBase: GetMuiComponentTheme<"MuiInputBase"> = (component, }, inputMultiline: { padding: `calc(${spacing(2)} - 1px)`, + resize: "vertical", }, inputAdornedStart: { paddingLeft: spacing(2), From a30f0ee4da3af0f5c3179dfef6099e438f5fd424 Mon Sep 17 00:00:00 2001 From: Kerstin Reichinger <70954479+kerstin97@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:20:12 +0100 Subject: [PATCH 002/266] Fix default and hover `border-color` of InputBase (#2910) Fix InputBase `border-color` in default state and on hover to match design. Co-authored-by: Kerstin Reichinger --- .changeset/early-ears-wave.md | 6 ++++++ .../admin/admin-theme/src/componentsTheme/MuiInputBase.ts | 6 +++++- packages/admin/admin/src/form/FieldContainer.tsx | 8 ++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 .changeset/early-ears-wave.md diff --git a/.changeset/early-ears-wave.md b/.changeset/early-ears-wave.md new file mode 100644 index 0000000000..1e41c8bdbd --- /dev/null +++ b/.changeset/early-ears-wave.md @@ -0,0 +1,6 @@ +--- +"@comet/admin-theme": patch +"@comet/admin": patch +--- + +Fix `border-color` of `InputBase` on default and hover state diff --git a/packages/admin/admin-theme/src/componentsTheme/MuiInputBase.ts b/packages/admin/admin-theme/src/componentsTheme/MuiInputBase.ts index 9e4ee9632f..01c2257ba3 100644 --- a/packages/admin/admin-theme/src/componentsTheme/MuiInputBase.ts +++ b/packages/admin/admin-theme/src/componentsTheme/MuiInputBase.ts @@ -7,7 +7,7 @@ export const getMuiInputBase: GetMuiComponentTheme<"MuiInputBase"> = (component, ...component, styleOverrides: mergeOverrideStyles<"MuiInputBase">(component?.styleOverrides, { root: { - border: `1px solid ${palette.grey[200]}`, + border: `1px solid ${palette.grey[100]}`, borderRadius: 2, backgroundColor: "#fff", @@ -19,6 +19,10 @@ export const getMuiInputBase: GetMuiComponentTheme<"MuiInputBase"> = (component, borderColor: palette.grey[100], backgroundColor: palette.grey[50], }, + + [`&:hover:not(.${inputBaseClasses.disabled}):not(.${inputBaseClasses.focused})`]: { + borderColor: palette.grey[200], + }, }, adornedStart: { paddingLeft: spacing(2), diff --git a/packages/admin/admin/src/form/FieldContainer.tsx b/packages/admin/admin/src/form/FieldContainer.tsx index ea2abb0b77..fec9753541 100644 --- a/packages/admin/admin/src/form/FieldContainer.tsx +++ b/packages/admin/admin/src/form/FieldContainer.tsx @@ -132,6 +132,10 @@ const InnerContainer = createComponentSlot("div") Date: Wed, 11 Dec 2024 10:29:48 +0100 Subject: [PATCH 003/266] Use static rendering for site (#2836) --- .../src/app/[domain]/[language]/layout.tsx | 2 ++ .../src/app/[domain]/[language]/not-found.tsx | 15 +++++++++++ demo/site/src/util/NotFoundContext.tsx | 25 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 demo/site/src/app/[domain]/[language]/not-found.tsx create mode 100644 demo/site/src/util/NotFoundContext.tsx diff --git a/demo/site/src/app/[domain]/[language]/layout.tsx b/demo/site/src/app/[domain]/[language]/layout.tsx index a923619fff..9d596fc4e4 100644 --- a/demo/site/src/app/[domain]/[language]/layout.tsx +++ b/demo/site/src/app/[domain]/[language]/layout.tsx @@ -1,5 +1,6 @@ import { IntlProvider } from "@src/util/IntlProvider"; import { loadMessages } from "@src/util/loadMessages"; +import { setNotFoundContext } from "@src/util/NotFoundContext"; import { getSiteConfigForDomain } from "@src/util/siteConfig"; import { PropsWithChildren } from "react"; @@ -8,6 +9,7 @@ export default async function Page({ children, params: { domain, language } }: P if (!siteConfig.scope.languages.includes(language)) { language = "en"; } + setNotFoundContext({ domain, language }); const messages = await loadMessages(language); return ( diff --git a/demo/site/src/app/[domain]/[language]/not-found.tsx b/demo/site/src/app/[domain]/[language]/not-found.tsx new file mode 100644 index 0000000000..7909dda1dd --- /dev/null +++ b/demo/site/src/app/[domain]/[language]/not-found.tsx @@ -0,0 +1,15 @@ +import { getNotFoundContext } from "@src/util/NotFoundContext"; +import Link from "next/link"; + +export default async function NotFound404(): Promise { + const scope = getNotFoundContext() || { domain: "main", language: "en" }; + + return ( + + +

Page not found (Scope {JSON.stringify(scope)}).

+ Return Home + + + ); +} diff --git a/demo/site/src/util/NotFoundContext.tsx b/demo/site/src/util/NotFoundContext.tsx new file mode 100644 index 0000000000..c5a79b3c8d --- /dev/null +++ b/demo/site/src/util/NotFoundContext.tsx @@ -0,0 +1,25 @@ +import type { ContentScope } from "@src/site-configs"; +import { cache } from "react"; + +// https://github.com/vercel/next.js/discussions/43179#discussioncomment-11192893 +/** + * This is a workaround until Next.js adds support for true Server Context + * It works by using React `cache` to store the value for the lifetime of one rendering. + * Meaning it's available to all server components down the tree after it's set. + * Do not use this unless necessary. + * + * @warning This is a temporary workaround. + */ +function createServerContext(defaultValue: T): [() => T, (v: T) => void] { + const getRef = cache(() => ({ current: defaultValue })); + + const getValue = (): T => getRef().current; + + const setValue = (value: T) => { + getRef().current = value; + }; + + return [getValue, setValue]; +} + +export const [getNotFoundContext, setNotFoundContext] = createServerContext(null); From 94cc411a657871b7a691c7575255703e6058c02f Mon Sep 17 00:00:00 2001 From: Kerstin Reichinger <70954479+kerstin97@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:30:42 +0100 Subject: [PATCH 004/266] `ContentScopeSelect` styling fixes (#2909) Co-authored-by: Kerstin Reichinger Co-authored-by: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> Co-authored-by: Ricky James Smith --- .changeset/brown-actors-enjoy.md | 5 + .../src/contentScope/ContentScopeSelect.tsx | 104 +++++++++++++----- 2 files changed, 81 insertions(+), 28 deletions(-) create mode 100644 .changeset/brown-actors-enjoy.md diff --git a/.changeset/brown-actors-enjoy.md b/.changeset/brown-actors-enjoy.md new file mode 100644 index 0000000000..1edeed38aa --- /dev/null +++ b/.changeset/brown-actors-enjoy.md @@ -0,0 +1,5 @@ +--- +"@comet/cms-admin": patch +--- + +Adapt styling of `ContentScopeSelect` to match the Comet design diff --git a/packages/admin/cms-admin/src/contentScope/ContentScopeSelect.tsx b/packages/admin/cms-admin/src/contentScope/ContentScopeSelect.tsx index 4bce3dcfc5..98d14fd6bb 100644 --- a/packages/admin/cms-admin/src/contentScope/ContentScopeSelect.tsx +++ b/packages/admin/cms-admin/src/contentScope/ContentScopeSelect.tsx @@ -1,6 +1,19 @@ import { AppHeaderDropdown, ClearInputAdornment } from "@comet/admin"; import { Domain, Search } from "@comet/admin-icons"; -import { Box, Divider, InputAdornment, InputBase, List, ListItem, ListItemButton, ListItemText, ListSubheader, Typography } from "@mui/material"; +import { + Box, + Divider, + InputAdornment, + InputBase, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + ListSubheader, + Typography, + useTheme, +} from "@mui/material"; import { capitalCase } from "change-case"; import { Fragment, ReactNode, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; @@ -35,6 +48,7 @@ export function ContentScopeSelect) { const intl = useIntl(); const [searchValue, setSearchValue] = useState(""); + const theme = useTheme(); const hasMultipleDimensions = Object.keys(value).length > 1; @@ -84,12 +98,18 @@ export function ContentScopeSelect option.label ?? option.value) .join(" – "); const matches = findTextMatches(text, query); + return ( - } - /> + <> + + + + } + /> + ); }; } @@ -111,9 +131,15 @@ export function ContentScopeSelect ({ minWidth: "350px", - }, + + [theme.breakpoints.down("md")]: { + width: "100%", + maxWidth: "none", + bottom: 0, + }, + }), }, }, }} @@ -122,11 +148,11 @@ export function ContentScopeSelect {searchable && ( <> - + - + } placeholder={intl.formatMessage({ @@ -140,6 +166,9 @@ export function ContentScopeSelect setSearchValue("")} hasClearableContent={searchValue !== ""} position="end" + slotProps={{ + buttonBase: { sx: { fontSize: "16px" } }, + }} /> } autoFocus @@ -149,7 +178,7 @@ export function ContentScopeSelect )} - + {groups.map((group, index) => { const showGroupHeader = hasMultipleDimensions; const showGroupDivider = showGroupHeader && index !== groups.length - 1; @@ -159,27 +188,46 @@ export function ContentScopeSelect {showGroupHeader && ( - theme.spacing(3) }}> - + ({ + paddingX: spacing(3), + paddingTop: spacing(4), + paddingBottom: spacing(2), + lineHeight: "inherit", + })} + > + theme.palette.grey[500]}> {matches ? : groupLabel} )} - {group.options.map((option) => ( - { - hideDropdown(); - onChange(optionToValue(option)); - setSearchValue(""); - }} - selected={option === selectedOption} - sx={{ paddingX: (theme) => theme.spacing(6) }} - > - {renderOption?.(option, searchValue)} - - ))} - {showGroupDivider && } + {group.options.map((option) => { + const isSelected = option === selectedOption; + + return ( + { + hideDropdown(); + onChange(optionToValue(option)); + setSearchValue(""); + }} + selected={isSelected} + sx={({ spacing }) => ({ + paddingX: spacing(6), + gap: spacing(2), + fontWeight: isSelected ? 600 : 250, + })} + > + {renderOption?.(option, searchValue)} + + ); + })} + {showGroupDivider && ( + ({ marginX: "8px", marginY: spacing(2), borderColor: palette.grey[50] })} + /> + )} ); })} From 9e837942a1633c428f1b6c0ab64cf159b2be4248 Mon Sep 17 00:00:00 2001 From: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:48:07 +0100 Subject: [PATCH 005/266] Site Cache Handler: set fallback cache only when Redis is not ready (#2904) --- demo/site/cache-handler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/site/cache-handler.ts b/demo/site/cache-handler.ts index a76898ce42..8f5e54eb5b 100644 --- a/demo/site/cache-handler.ts +++ b/demo/site/cache-handler.ts @@ -137,7 +137,7 @@ export default class CacheHandler { const responseBody = parseBodyForGqlError(value.data.body); if (responseBody?.errors) { // Must not cache GraphQL errors - console.error("CacheHandler.set GraphQL Error: ", responseBody.error); + console.error("CacheHandler.set GraphQL Error: ", responseBody.errors); return; } } @@ -157,6 +157,7 @@ export default class CacheHandler { } catch (e) { console.error("CacheHandler.set error", e); } + return; } if (CACHE_HANDLER_DEBUG) { console.log("CacheHandler.set fallbackCache", key); From 47d40ae01b12c7169ca3d396ab7c1923cceea20c Mon Sep 17 00:00:00 2001 From: fichtnerma <72387685+fichtnerma@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:04:17 +0100 Subject: [PATCH 006/266] Switch external DAM integration to Lorem Picsum (#2918) Co-authored-by: Markus Fichtner Co-authored-by: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> --- demo/admin/src/App.tsx | 8 ++--- demo/admin/src/common/MasterMenu.tsx | 4 +-- ...tFromUnsplash.tsx => ImportFromPicsum.tsx} | 34 +++++++++---------- demo/admin/src/dam/PicsumIcon.tsx | 10 ++++++ demo/admin/src/dam/UnsplashIcon.tsx | 9 ----- ...splashImage.ts => getRandomPicsumImage.ts} | 15 ++++---- 6 files changed, 41 insertions(+), 39 deletions(-) rename demo/admin/src/dam/{ImportFromUnsplash.tsx => ImportFromPicsum.tsx} (63%) create mode 100644 demo/admin/src/dam/PicsumIcon.tsx delete mode 100644 demo/admin/src/dam/UnsplashIcon.tsx rename demo/admin/src/dam/{getRandomUnsplashImage.ts => getRandomPicsumImage.ts} (63%) diff --git a/demo/admin/src/App.tsx b/demo/admin/src/App.tsx index 6a4891ddb6..07707bdc75 100644 --- a/demo/admin/src/App.tsx +++ b/demo/admin/src/App.tsx @@ -23,7 +23,6 @@ import { createApolloClient } from "@src/common/apollo/createApolloClient"; import ContentScopeProvider, { ContentScope } from "@src/common/ContentScopeProvider"; import { additionalPageTreeNodeFieldsFragment } from "@src/common/EditPageNode"; import { ConfigProvider, createConfig } from "@src/config"; -import { ImportFromUnsplash } from "@src/dam/ImportFromUnsplash"; import { pageTreeCategories } from "@src/pageTree/pageTreeCategories"; import { theme } from "@src/theme"; import { HTML5toTouch } from "rdndmb-html5-to-touch"; @@ -35,6 +34,7 @@ import { Route, Switch } from "react-router-dom"; import MasterHeader from "./common/MasterHeader"; import MasterMenu, { masterMenuData, pageTreeDocumentTypes } from "./common/MasterMenu"; +import { ImportFromPicsum } from "./dam/ImportFromPicsum"; import { getMessages } from "./lang"; import { Link } from "./links/Link"; import { NewsDependency } from "./news/dependencies/NewsDependency"; @@ -84,10 +84,10 @@ class App extends Component { , + additionalToolbarItems: , importSources: { - unsplash: { - label: , + picsum: { + label: , }, }, contentGeneration: { diff --git a/demo/admin/src/common/MasterMenu.tsx b/demo/admin/src/common/MasterMenu.tsx index a31cba476e..0ff1b45532 100644 --- a/demo/admin/src/common/MasterMenu.tsx +++ b/demo/admin/src/common/MasterMenu.tsx @@ -13,7 +13,7 @@ import { UserPermissionsPage, } from "@comet/cms-admin"; import { ContentScope } from "@src/common/ContentScopeProvider"; -import { ImportFromUnsplash } from "@src/dam/ImportFromUnsplash"; +import { ImportFromPicsum } from "@src/dam/ImportFromPicsum"; import Dashboard from "@src/dashboard/Dashboard"; import { PredefinedPage } from "@src/documents/predefinedPages/PredefinedPage"; import { GQLPageTreeNodeCategory } from "@src/graphql.generated"; @@ -124,7 +124,7 @@ export const masterMenuData: MasterMenuData = [ icon: , route: { path: "/assets", - render: () => } />, + render: () => } />, }, requiredPermission: "dam", }, diff --git a/demo/admin/src/dam/ImportFromUnsplash.tsx b/demo/admin/src/dam/ImportFromPicsum.tsx similarity index 63% rename from demo/admin/src/dam/ImportFromUnsplash.tsx rename to demo/admin/src/dam/ImportFromPicsum.tsx index 9f03732e69..80f0e8da77 100644 --- a/demo/admin/src/dam/ImportFromUnsplash.tsx +++ b/demo/admin/src/dam/ImportFromPicsum.tsx @@ -5,22 +5,22 @@ import { styled } from "@mui/material/styles"; import { useState } from "react"; import { FormattedMessage } from "react-intl"; -import { getRandomUnsplashImage, UnsplashImage } from "./getRandomUnsplashImage"; -import UnsplashIcon from "./UnsplashIcon"; +import { getRandomPicsumImage, PicsumImage } from "./getRandomPicsumImage"; +import PicsumIcon from "./PicsumIcon"; -export const ImportFromUnsplash = () => { +export const ImportFromPicsum = () => { const { allAcceptedMimeTypes } = useDamAcceptedMimeTypes(); const { folderId } = useCurrentDamFolder(); const [isOpen, setIsOpen] = useState(false); - const [unsplashImage, setUnsplashImage] = useState(); + const [picsumImage, setPicsumImage] = useState(); const { uploadFiles } = useDamFileUpload({ acceptedMimetypes: allAcceptedMimeTypes, }); const handleOpenDialog = async () => { - const image = await getRandomUnsplashImage(); - setUnsplashImage(image); + const image = await getRandomPicsumImage(); + setPicsumImage(image); setIsOpen(true); }; @@ -29,14 +29,14 @@ export const ImportFromUnsplash = () => { }; const handleSave = async () => { - if (unsplashImage === undefined) return; + if (picsumImage === undefined) return; await uploadFiles( - { acceptedFiles: [unsplashImage.file], fileRejections: [] }, + { acceptedFiles: [picsumImage.file], fileRejections: [] }, { folderId, importSource: { - importSourceId: unsplashImage.url, - importSourceType: "unsplash", + importSourceId: picsumImage.url, + importSourceType: "picsum", }, }, ); @@ -44,25 +44,25 @@ export const ImportFromUnsplash = () => { }; const handleShuffle = async () => { - const image = await getRandomUnsplashImage(); - setUnsplashImage(image); + const image = await getRandomPicsumImage(); + setPicsumImage(image); }; return ( <> -
- Import from Unsplash + Import from Picsum - + diff --git a/demo/admin/src/dam/PicsumIcon.tsx b/demo/admin/src/dam/PicsumIcon.tsx new file mode 100644 index 0000000000..23ce6b3af1 --- /dev/null +++ b/demo/admin/src/dam/PicsumIcon.tsx @@ -0,0 +1,10 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function PicsumIcon(props: SvgIconProps): JSX.Element { + return ( + + + + + ); +} diff --git a/demo/admin/src/dam/UnsplashIcon.tsx b/demo/admin/src/dam/UnsplashIcon.tsx deleted file mode 100644 index 97322d8d48..0000000000 --- a/demo/admin/src/dam/UnsplashIcon.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { SvgIcon, SvgIconProps } from "@mui/material"; - -export default function UnsplashIcon(props: SvgIconProps): JSX.Element { - return ( - - - - ); -} diff --git a/demo/admin/src/dam/getRandomUnsplashImage.ts b/demo/admin/src/dam/getRandomPicsumImage.ts similarity index 63% rename from demo/admin/src/dam/getRandomUnsplashImage.ts rename to demo/admin/src/dam/getRandomPicsumImage.ts index d056e9c60e..edcee57ec7 100644 --- a/demo/admin/src/dam/getRandomUnsplashImage.ts +++ b/demo/admin/src/dam/getRandomPicsumImage.ts @@ -1,9 +1,9 @@ -export interface UnsplashImage { +export interface PicsumImage { file: File; url: string; } -async function fetchUnsplashImage(url: string) { +async function fetchPicsumImage(url: string) { const response = await fetch(url); if (!response.ok) { @@ -18,19 +18,20 @@ async function fetchUnsplashImage(url: string) { function extractFileNameFromUrl(url: string): string { const fileNameWithQuery = url.split("?")[0]; - const fileName = fileNameWithQuery.split("/").pop(); + const fileNameStart = fileNameWithQuery.split("/").indexOf("id"); + const fileName = `${fileNameWithQuery.split("/")[fileNameStart]}-${fileNameWithQuery.split("/")[fileNameStart + 1]}`; return fileName ? `${fileName}.jpeg` : "unnamed.jpeg"; } -export async function getRandomUnsplashImage(): Promise { - const imageUrl = "https://source.unsplash.com/all/"; +export async function getRandomPicsumImage(): Promise { + const imageUrl = "https://picsum.photos/1920/1080"; try { - const image = await fetchUnsplashImage(imageUrl); + const image = await fetchPicsumImage(imageUrl); const mimeType = image.blob.type; if (mimeType !== "image/jpeg") { - return getRandomUnsplashImage(); + return getRandomPicsumImage(); } const fileName = extractFileNameFromUrl(image.origin); From 77f1889044538d294343809e7684a821014427ac Mon Sep 17 00:00:00 2001 From: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:36:02 +0100 Subject: [PATCH 007/266] Generate news with future Admin Generator (#2661) --- demo/admin/crud-generator-config.ts | 4 - demo/admin/src/common/MasterMenu.tsx | 2 +- demo/admin/src/news/NewsForm.cometGen.ts | 48 ++++++ demo/admin/src/news/NewsGrid.cometGen.ts | 38 +++++ demo/admin/src/news/NewsPage.tsx | 65 ++++++++ .../{NewsForm.gql.ts => NewsForm.gql.tsx} | 17 +- demo/admin/src/news/generated/NewsForm.tsx | 156 ++++++++---------- demo/admin/src/news/generated/NewsGrid.tsx | 124 ++++++-------- demo/admin/src/news/generated/NewsPage.tsx | 30 ---- 9 files changed, 275 insertions(+), 209 deletions(-) create mode 100644 demo/admin/src/news/NewsForm.cometGen.ts create mode 100644 demo/admin/src/news/NewsGrid.cometGen.ts create mode 100644 demo/admin/src/news/NewsPage.tsx rename demo/admin/src/news/generated/{NewsForm.gql.ts => NewsForm.gql.tsx} (80%) delete mode 100644 demo/admin/src/news/generated/NewsPage.tsx diff --git a/demo/admin/crud-generator-config.ts b/demo/admin/crud-generator-config.ts index f0d7356725..1b71110f32 100644 --- a/demo/admin/crud-generator-config.ts +++ b/demo/admin/crud-generator-config.ts @@ -5,8 +5,4 @@ export default [ target: "src/products/generated", entityName: "Product", }, - { - target: "src/news/generated", - entityName: "News", - }, ] satisfies CrudGeneratorConfig[]; diff --git a/demo/admin/src/common/MasterMenu.tsx b/demo/admin/src/common/MasterMenu.tsx index 0ff1b45532..d1b64136d7 100644 --- a/demo/admin/src/common/MasterMenu.tsx +++ b/demo/admin/src/common/MasterMenu.tsx @@ -19,7 +19,7 @@ import { PredefinedPage } from "@src/documents/predefinedPages/PredefinedPage"; import { GQLPageTreeNodeCategory } from "@src/graphql.generated"; import { Link } from "@src/links/Link"; import { NewsLinkBlock } from "@src/news/blocks/NewsLinkBlock"; -import { NewsPage } from "@src/news/generated/NewsPage"; +import { NewsPage } from "@src/news/NewsPage"; import MainMenu from "@src/pages/mainMenu/MainMenu"; import { Page } from "@src/pages/Page"; import { categoryToUrlParam, pageTreeCategories, urlParamToCategory } from "@src/pageTree/pageTreeCategories"; diff --git a/demo/admin/src/news/NewsForm.cometGen.ts b/demo/admin/src/news/NewsForm.cometGen.ts new file mode 100644 index 0000000000..2076f679f5 --- /dev/null +++ b/demo/admin/src/news/NewsForm.cometGen.ts @@ -0,0 +1,48 @@ +import { future_FormConfig as FormConfig } from "@comet/cms-admin"; +import { GQLNews } from "@src/graphql.generated"; + +export const NewsForm: FormConfig = { + type: "form", + gqlType: "News", + fragmentName: "NewsForm", + fields: [ + { + type: "text", + name: "slug", + label: "Slug", + required: true, + }, + { + type: "text", + name: "title", + label: "Title", + required: true, + }, + { + type: "date", + name: "date", + label: "Date", + required: true, + }, + { + type: "staticSelect", + name: "category", + label: "Category", + required: true, + inputType: "radio", + values: ["Events", "Company", "Awards"], + }, + { + type: "block", + name: "image", + label: "Image", + block: { name: "DamImageBlock", import: "@comet/cms-admin" }, + }, + { + type: "block", + name: "content", + label: "Content", + block: { name: "NewsContentBlock", import: "../blocks/NewsContentBlock" }, + }, + ], +}; diff --git a/demo/admin/src/news/NewsGrid.cometGen.ts b/demo/admin/src/news/NewsGrid.cometGen.ts new file mode 100644 index 0000000000..bd3adbe514 --- /dev/null +++ b/demo/admin/src/news/NewsGrid.cometGen.ts @@ -0,0 +1,38 @@ +import { future_GridConfig as GridConfig } from "@comet/cms-admin"; +import { GQLNews } from "@src/graphql.generated"; + +export const NewsGrid: GridConfig = { + type: "grid", + gqlType: "News", + fragmentName: "NewsGrid", + columns: [ + { + type: "text", + name: "title", + headerName: "Title", + }, + { + type: "date", + name: "date", + headerName: "Date", + }, + { + type: "staticSelect", + name: "category", + headerName: "Category", + values: ["Events", "Company", "Awards"], + }, + { + type: "block", + name: "image", + headerName: "Image", + block: { name: "DamImageBlock", import: "@comet/cms-admin" }, + }, + { + type: "block", + name: "content", + headerName: "Content", + block: { name: "NewsContentBlock", import: "../blocks/NewsContentBlock" }, + }, + ], +}; diff --git a/demo/admin/src/news/NewsPage.tsx b/demo/admin/src/news/NewsPage.tsx new file mode 100644 index 0000000000..2a5816529a --- /dev/null +++ b/demo/admin/src/news/NewsPage.tsx @@ -0,0 +1,65 @@ +import { + MainContent, + SaveBoundary, + SaveBoundarySaveButton, + Stack, + StackPage, + StackSwitch, + StackToolbar, + ToolbarActions, + ToolbarAutomaticTitleItem, + ToolbarBackButton, + ToolbarFillSpace, +} from "@comet/admin"; +import { ContentScopeIndicator } from "@comet/cms-admin"; +import { useContentScope } from "@src/common/ContentScopeProvider"; +import { useIntl } from "react-intl"; + +import { NewsForm } from "./generated/NewsForm"; +import { NewsGrid } from "./generated/NewsGrid"; + +const FormToolbar = () => ( + }> + + + + + + + +); + +export function NewsPage() { + const intl = useIntl(); + const { scope } = useContentScope(); + return ( + + + + } /> + + + + + + {(selectedNewsId) => ( + + + + + + + )} + + + + + + + + + + + + ); +} diff --git a/demo/admin/src/news/generated/NewsForm.gql.ts b/demo/admin/src/news/generated/NewsForm.gql.tsx similarity index 80% rename from demo/admin/src/news/generated/NewsForm.gql.ts rename to demo/admin/src/news/generated/NewsForm.gql.tsx index 1bd9172e08..c675e18777 100644 --- a/demo/admin/src/news/generated/NewsForm.gql.ts +++ b/demo/admin/src/news/generated/NewsForm.gql.tsx @@ -1,22 +1,19 @@ // This file has been generated by comet admin-generator. // You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment. - import { gql } from "@apollo/client"; export const newsFormFragment = gql` fragment NewsForm on News { slug title - status date category image content } `; - -export const newsFormQuery = gql` - query NewsForm($id: ID!) { +export const newsQuery = gql` + query News($id: ID!) { news(id: $id) { id updatedAt @@ -25,15 +22,6 @@ export const newsFormQuery = gql` } ${newsFormFragment} `; - -export const newsFormCheckForChangesQuery = gql` - query NewsFormCheckForChanges($id: ID!) { - news(id: $id) { - updatedAt - } - } -`; - export const createNewsMutation = gql` mutation CreateNews($scope: NewsContentScopeInput!, $input: NewsInput!) { createNews(scope: $scope, input: $input) { @@ -44,7 +32,6 @@ export const createNewsMutation = gql` } ${newsFormFragment} `; - export const updateNewsMutation = gql` mutation UpdateNews($id: ID!, $input: NewsUpdateInput!) { updateNews(id: $id, input: $input) { diff --git a/demo/admin/src/news/generated/NewsForm.tsx b/demo/admin/src/news/generated/NewsForm.tsx index b3d1196f8f..bce42af277 100644 --- a/demo/admin/src/news/generated/NewsForm.tsx +++ b/demo/admin/src/news/generated/NewsForm.tsx @@ -1,45 +1,34 @@ // This file has been generated by comet admin-generator. // You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment. - import { useApolloClient, useQuery } from "@apollo/client"; import { Field, filterByFragment, FinalForm, - FinalFormSaveButton, - FinalFormSelect, FinalFormSubmitEvent, Loading, - MainContent, + RadioGroupField, TextField, - Toolbar, - ToolbarActions, - ToolbarFillSpace, - ToolbarItem, - ToolbarTitleItem, useFormApiRef, - useStackApi, useStackSwitchApi, } from "@comet/admin"; -import { DateField } from "@comet/admin-date-time"; -import { ArrowLeft } from "@comet/admin-icons"; +import { FinalFormDatePicker } from "@comet/admin-date-time"; import { BlockState, createFinalFormBlock } from "@comet/blocks-admin"; -import { ContentScopeIndicator, DamImageBlock, queryUpdatedAt, resolveHasSaveConflict, useFormSaveConflict } from "@comet/cms-admin"; -import { IconButton, MenuItem } from "@mui/material"; -import { useContentScope } from "@src/common/ContentScopeProvider"; +import { DamImageBlock, queryUpdatedAt, resolveHasSaveConflict, useFormSaveConflict } from "@comet/cms-admin"; +import { GQLNewsContentScopeInput } from "@src/graphql.generated"; import { FormApi } from "final-form"; import isEqual from "lodash.isequal"; import React from "react"; import { FormattedMessage } from "react-intl"; import { NewsContentBlock } from "../blocks/NewsContentBlock"; -import { createNewsMutation, newsFormFragment, newsFormQuery, updateNewsMutation } from "./NewsForm.gql"; +import { createNewsMutation, newsFormFragment, newsQuery, updateNewsMutation } from "./NewsForm.gql"; import { GQLCreateNewsMutation, GQLCreateNewsMutationVariables, GQLNewsFormFragment, - GQLNewsFormQuery, - GQLNewsFormQueryVariables, + GQLNewsQuery, + GQLNewsQueryVariables, GQLUpdateNewsMutation, GQLUpdateNewsMutationVariables, } from "./NewsForm.gql.generated"; @@ -56,27 +45,23 @@ type FormValues = GQLNewsFormFragment & { interface FormProps { id?: string; + scope: GQLNewsContentScopeInput; } -export function NewsForm({ id }: FormProps): React.ReactElement { - const stackApi = useStackApi(); +export function NewsForm({ id, scope }: FormProps): React.ReactElement { const client = useApolloClient(); const mode = id ? "edit" : "add"; const formApiRef = useFormApiRef(); const stackSwitchApi = useStackSwitchApi(); - const { scope } = useContentScope(); - const { data, error, loading, refetch } = useQuery( - newsFormQuery, - id ? { variables: { id } } : { skip: true }, - ); + const { data, error, loading, refetch } = useQuery(newsQuery, id ? { variables: { id } } : { skip: true }); const initialValues = React.useMemo>( () => data?.news ? { ...filterByFragment(newsFormFragment, data.news), - + date: data.news.date ? new Date(data.news.date) : undefined, image: rootBlocks.image.input2State(data.news.image), content: rootBlocks.content.input2State(data.news.content), } @@ -98,36 +83,30 @@ export function NewsForm({ id }: FormProps): React.ReactElement { }, }); - const handleSubmit = async (state: FormValues, form: FormApi, event: FinalFormSubmitEvent) => { - if (await saveConflict.checkForConflicts()) { - throw new Error("Conflicts detected"); - } - + const handleSubmit = async (formValues: FormValues, form: FormApi, event: FinalFormSubmitEvent) => { + if (await saveConflict.checkForConflicts()) throw new Error("Conflicts detected"); const output = { - ...state, - - image: rootBlocks.image.state2Output(state.image), - content: rootBlocks.content.state2Output(state.content), + ...formValues, + image: rootBlocks.image.state2Output(formValues.image), + content: rootBlocks.content.state2Output(formValues.content), }; - if (mode === "edit") { - if (!id) { - throw new Error("Missing id in edit mode"); - } + if (!id) throw new Error(); + const { ...updateInput } = output; await client.mutate({ mutation: updateNewsMutation, - variables: { id, input: output }, + variables: { id, input: updateInput }, }); } else { const { data: mutationResponse } = await client.mutate({ mutation: createNewsMutation, - variables: { scope, input: output }, + variables: { input: output, scope }, }); if (!event.navigatingBack) { const id = mutationResponse?.createNews.id; if (id) { setTimeout(() => { - stackSwitchApi.activatePage("edit", id); + stackSwitchApi.activatePage(`edit`, id); }); } } @@ -141,25 +120,18 @@ export function NewsForm({ id }: FormProps): React.ReactElement { } return ( - apiRef={formApiRef} onSubmit={handleSubmit} mode={mode} initialValues={initialValues}> - {({ values }) => ( + + apiRef={formApiRef} + onSubmit={handleSubmit} + mode={mode} + initialValues={initialValues} + initialValuesEqual={isEqual} //required to compare block data correctly + subscription={{}} + > + {() => ( <> {saveConflict.dialogs} - }> - - - - - - - - - - - - - - + <> } /> + } /> - }> - {(props) => ( - - - - - - - - - )} - - } /> - } + options={[ + { + label: , + value: "Events", + }, + { + label: , + value: "Company", + }, + { + label: , + value: "Awards", + }, + ]} + /> + } + variant="horizontal" + fullWidth > - {(props) => ( - - - - - - - - - - - - )} - - {createFinalFormBlock(rootBlocks.image)} - + } + variant="horizontal" + fullWidth + > {createFinalFormBlock(rootBlocks.content)} - + )} diff --git a/demo/admin/src/news/generated/NewsGrid.tsx b/demo/admin/src/news/generated/NewsGrid.tsx index 929bccb852..123fd54a8b 100644 --- a/demo/admin/src/news/generated/NewsGrid.tsx +++ b/demo/admin/src/news/generated/NewsGrid.tsx @@ -4,11 +4,12 @@ import { gql, useApolloClient, useQuery } from "@apollo/client"; import { CrudContextMenu, DataGridToolbar, + filterByFragment, GridColDef, GridFilterButton, - MainContent, muiGridFilterToGql, muiGridSortToGql, + renderStaticSelectCell, StackLink, ToolbarActions, ToolbarFillSpace, @@ -17,7 +18,7 @@ import { useDataGridRemote, usePersistentColumnState, } from "@comet/admin"; -import { Add as AddIcon, Edit } from "@comet/admin-icons"; +import { Add as AddIcon, Edit as EditIcon } from "@comet/admin-icons"; import { BlockPreviewContent } from "@comet/blocks-admin"; import { DamImageBlock } from "@comet/cms-admin"; import { Button, IconButton } from "@mui/material"; @@ -32,31 +33,27 @@ import { GQLCreateNewsMutationVariables, GQLDeleteNewsMutation, GQLDeleteNewsMutationVariables, + GQLNewsGridFragment, GQLNewsGridQuery, GQLNewsGridQueryVariables, - GQLNewsListFragment, } from "./NewsGrid.generated"; const newsFragment = gql` - fragment NewsList on News { + fragment NewsGrid on News { id - slug title - status date category image content - createdAt - updatedAt } `; const newsQuery = gql` - query NewsGrid($offset: Int, $limit: Int, $sort: [NewsSort!], $search: String, $filter: NewsFilter, $scope: NewsContentScopeInput!) { + query NewsGrid($offset: Int!, $limit: Int!, $sort: [NewsSort!], $search: String, $filter: NewsFilter, $scope: NewsContentScopeInput!) { newsList(offset: $offset, limit: $limit, sort: $sort, search: $search, filter: $filter, scope: $scope) { nodes { - ...NewsList + ...NewsGrid } totalCount } @@ -103,70 +100,61 @@ export function NewsGrid(): React.ReactElement { const dataGridProps = { ...useDataGridRemote(), ...usePersistentColumnState("NewsGrid") }; const { scope } = useContentScope(); - const columns: GridColDef[] = [ - { field: "slug", headerName: intl.formatMessage({ id: "news.slug", defaultMessage: "Slug" }), width: 150 }, - { field: "title", headerName: intl.formatMessage({ id: "news.title", defaultMessage: "Title" }), width: 150 }, - { - field: "status", - headerName: intl.formatMessage({ id: "news.status", defaultMessage: "Status" }), - type: "singleSelect", - valueOptions: [ - { value: "Active", label: intl.formatMessage({ id: "news.status.active", defaultMessage: "Active" }) }, - { value: "Deleted", label: intl.formatMessage({ id: "news.status.deleted", defaultMessage: "Deleted" }) }, - ], - width: 150, - }, + const columns: GridColDef[] = [ + { field: "title", headerName: intl.formatMessage({ id: "news.title", defaultMessage: "Title" }), flex: 1, minWidth: 150 }, { field: "date", headerName: intl.formatMessage({ id: "news.date", defaultMessage: "Date" }), - type: "dateTime", - valueGetter: ({ value }) => value && new Date(value), - width: 150, + type: "date", + valueGetter: ({ row }) => row.date && new Date(row.date), + valueFormatter: ({ value }) => (value ? intl.formatDate(value) : ""), + flex: 1, + minWidth: 150, }, { field: "category", headerName: intl.formatMessage({ id: "news.category", defaultMessage: "Category" }), type: "singleSelect", + valueFormatter: ({ value }) => value?.toString(), valueOptions: [ - { value: "Events", label: intl.formatMessage({ id: "news.category.events", defaultMessage: "Events" }) }, - { value: "Company", label: intl.formatMessage({ id: "news.category.company", defaultMessage: "Company" }) }, - { value: "Awards", label: intl.formatMessage({ id: "news.category.awards", defaultMessage: "Awards" }) }, + { + value: "Events", + label: intl.formatMessage({ id: "news.category.events", defaultMessage: "Events" }), + }, + { + value: "Company", + label: intl.formatMessage({ id: "news.category.company", defaultMessage: "Company" }), + }, + { + value: "Awards", + label: intl.formatMessage({ id: "news.category.awards", defaultMessage: "Awards" }), + }, ], - width: 150, + renderCell: renderStaticSelectCell, + flex: 1, + minWidth: 150, }, { field: "image", headerName: intl.formatMessage({ id: "news.image", defaultMessage: "Image" }), filterable: false, sortable: false, - width: 150, renderCell: (params) => { return ; }, + flex: 1, + minWidth: 150, }, { field: "content", headerName: intl.formatMessage({ id: "news.content", defaultMessage: "Content" }), filterable: false, sortable: false, - width: 150, renderCell: (params) => { return ; }, - }, - { - field: "createdAt", - headerName: intl.formatMessage({ id: "news.createdAt", defaultMessage: "Created At" }), - type: "dateTime", - valueGetter: ({ value }) => value && new Date(value), - width: 150, - }, - { - field: "updatedAt", - headerName: intl.formatMessage({ id: "news.updatedAt", defaultMessage: "Updated At" }), - type: "dateTime", - valueGetter: ({ value }) => value && new Date(value), - width: 150, + flex: 1, + minWidth: 150, }, { field: "actions", @@ -174,23 +162,23 @@ export function NewsGrid(): React.ReactElement { sortable: false, filterable: false, type: "actions", + align: "right", + pinned: "right", + width: 84, renderCell: (params) => { return ( <> - - + + { - const row = params.row; + // Don't copy id, because we want to create a new entity with this data + const { id, ...filteredData } = filterByFragment(newsFragment, params.row); return { - slug: row.slug, - title: row.title, - status: row.status, - date: row.date, - category: row.category, - image: DamImageBlock.state2Output(DamImageBlock.input2State(row.image)), - content: NewsContentBlock.state2Output(NewsContentBlock.input2State(row.content)), + ...filteredData, + image: DamImageBlock.state2Output(DamImageBlock.input2State(filteredData.image)), + content: NewsContentBlock.state2Output(NewsContentBlock.input2State(filteredData.content)), }; }} onPaste={async ({ input }) => { @@ -230,18 +218,16 @@ export function NewsGrid(): React.ReactElement { const rows = data?.newsList.nodes ?? []; return ( - - - + ); } diff --git a/demo/admin/src/news/generated/NewsPage.tsx b/demo/admin/src/news/generated/NewsPage.tsx deleted file mode 100644 index be3a73e2f1..0000000000 --- a/demo/admin/src/news/generated/NewsPage.tsx +++ /dev/null @@ -1,30 +0,0 @@ -// This file has been generated by comet admin-generator. -// You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment. - -import { Stack, StackPage, StackSwitch, StackToolbar } from "@comet/admin"; -import { ContentScopeIndicator } from "@comet/cms-admin"; -import * as React from "react"; -import { useIntl } from "react-intl"; - -import { NewsForm } from "./NewsForm"; -import { NewsGrid } from "./NewsGrid"; - -export function NewsPage(): React.ReactElement { - const intl = useIntl(); - return ( - - - - } /> - - - - {(selectedId) => } - - - - - - - ); -} From 595dfaf6a6c0cab1c7cdb4daad5d1e3ad4bc8a1e Mon Sep 17 00:00:00 2001 From: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:40:05 +0100 Subject: [PATCH 008/266] Remove example comment from pull request template (#2923) --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4be089d2c0..09a6e9b3ed 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,7 +2,7 @@ ## Acceptance criteria -- [ ] I have verified if my change requires [an example](https://github.com/vivid-planet/comet/blob/HEAD/CONTRIBUTING.md#example): +- [ ] I have verified if my change requires [an example](https://github.com/vivid-planet/comet/blob/HEAD/CONTRIBUTING.md#example) - [ ] I have verified if my change requires [a changeset](https://github.com/vivid-planet/comet/blob/HEAD/CONTRIBUTING.md#changeset) - [ ] I have verified if my change requires [screenshots/screencasts](https://github.com/vivid-planet/comet/blob/HEAD/CONTRIBUTING.md#screenshotsscreencasts) From fcec4c5aa8642cfa76c5f0496eedcc8d253dfca6 Mon Sep 17 00:00:00 2001 From: Kerstin Reichinger <70954479+kerstin97@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:00:28 +0100 Subject: [PATCH 009/266] Fix vertical centering for `GridCellContent` (#2924) Co-authored-by: Kerstin Reichinger --- packages/admin/admin/src/dataGrid/GridCellContent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/admin/admin/src/dataGrid/GridCellContent.tsx b/packages/admin/admin/src/dataGrid/GridCellContent.tsx index ec717b0db9..2fc4e759b7 100644 --- a/packages/admin/admin/src/dataGrid/GridCellContent.tsx +++ b/packages/admin/admin/src/dataGrid/GridCellContent.tsx @@ -60,6 +60,7 @@ const Root = createComponentSlot("div")({ gap: ${theme.spacing(2)}; overflow: hidden; line-height: 0; + height: 100%; `, ); From 4141eef8838a77ddce0e9d176049e25bd94e7228 Mon Sep 17 00:00:00 2001 From: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:03:42 +0100 Subject: [PATCH 010/266] Demo Admin: Regenerate news (#2933) --- demo/admin/src/news/generated/NewsGrid.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/demo/admin/src/news/generated/NewsGrid.tsx b/demo/admin/src/news/generated/NewsGrid.tsx index 123fd54a8b..0de188a797 100644 --- a/demo/admin/src/news/generated/NewsGrid.tsx +++ b/demo/admin/src/news/generated/NewsGrid.tsx @@ -3,6 +3,7 @@ import { gql, useApolloClient, useQuery } from "@apollo/client"; import { CrudContextMenu, + dataGridDateColumn, DataGridToolbar, filterByFragment, GridColDef, @@ -102,15 +103,7 @@ export function NewsGrid(): React.ReactElement { const columns: GridColDef[] = [ { field: "title", headerName: intl.formatMessage({ id: "news.title", defaultMessage: "Title" }), flex: 1, minWidth: 150 }, - { - field: "date", - headerName: intl.formatMessage({ id: "news.date", defaultMessage: "Date" }), - type: "date", - valueGetter: ({ row }) => row.date && new Date(row.date), - valueFormatter: ({ value }) => (value ? intl.formatDate(value) : ""), - flex: 1, - minWidth: 150, - }, + { ...dataGridDateColumn, field: "date", headerName: intl.formatMessage({ id: "news.date", defaultMessage: "Date" }), flex: 1, minWidth: 150 }, { field: "category", headerName: intl.formatMessage({ id: "news.category", defaultMessage: "Category" }), From 3792ee1c4f124fd030457eb3edec506eeb3e848f Mon Sep 17 00:00:00 2001 From: Ricky James Smith Date: Mon, 16 Dec 2024 15:21:45 +0100 Subject: [PATCH 011/266] Add dev-story for `FeedbackButton` (#2931) --- .../src/admin/FeedbackButton.stories.tsx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 storybook/src/admin/FeedbackButton.stories.tsx diff --git a/storybook/src/admin/FeedbackButton.stories.tsx b/storybook/src/admin/FeedbackButton.stories.tsx new file mode 100644 index 0000000000..1beaa51d89 --- /dev/null +++ b/storybook/src/admin/FeedbackButton.stories.tsx @@ -0,0 +1,54 @@ +import { FeedbackButton } from "@comet/admin"; +import { Add } from "@comet/admin-icons"; +import { Card, CardContent, Stack } from "@mui/material"; + +export default { + title: "@comet/admin/FeedbackButton", +}; + +export const Default = { + render: () => ( + + + + } + onClick={() => { + return new Promise((resolve) => setTimeout(resolve, 500)); + }} + > + This will succeed + + } + onClick={() => { + return new Promise((_, reject) => setTimeout(reject, 500)); + }} + > + This will fail + + } + onClick={() => { + return new Promise((resolve) => setTimeout(resolve, 500)); + }} + tooltipErrorMessage="This failed but at least it has a custom message" + tooltipSuccessMessage="This worked and has a custom message" + > + Custom message (succeeds) + + } + onClick={() => { + return new Promise((_, reject) => setTimeout(reject, 500)); + }} + tooltipErrorMessage="This failed but at least it has a custom message" + tooltipSuccessMessage="This worked and has a custom message" + > + Custom message (fails) + + + + + ), +}; From 8649d179c1ce443f3fb4b1aef31cd57453bd3b41 Mon Sep 17 00:00:00 2001 From: Niko Sams Date: Tue, 17 Dec 2024 11:37:11 +0100 Subject: [PATCH 012/266] Demo Site: production tracing: Add RuntimeNodeInstrumentation (#2908) --- demo/site/package.json | 1 + demo/site/tracing.production.ts | 7 ++- pnpm-lock.yaml | 81 +++++++++++++++++++++++---------- 3 files changed, 63 insertions(+), 26 deletions(-) diff --git a/demo/site/package.json b/demo/site/package.json index b6ebfc5254..cad9fd83c9 100644 --- a/demo/site/package.json +++ b/demo/site/package.json @@ -29,6 +29,7 @@ "@opentelemetry/core": "^1.26.0", "@opentelemetry/exporter-prometheus": "^0.53.0", "@opentelemetry/exporter-trace-otlp-http": "^0.53.0", + "@opentelemetry/instrumentation-runtime-node": "^0.11.0", "@opentelemetry/sdk-metrics": "^1.26.0", "@opentelemetry/sdk-node": "^0.53.0", "cache-manager": "^5.5.3", diff --git a/demo/site/tracing.production.ts b/demo/site/tracing.production.ts index 7787d5ea31..89f5cabd8b 100644 --- a/demo/site/tracing.production.ts +++ b/demo/site/tracing.production.ts @@ -1,4 +1,5 @@ import { PrometheusExporter } from "@opentelemetry/exporter-prometheus"; +import { RuntimeNodeInstrumentation } from "@opentelemetry/instrumentation-runtime-node"; import { NodeSDK } from "@opentelemetry/sdk-node"; //diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO); @@ -9,7 +10,11 @@ const sdk = new NodeSDK({ // eslint-disable-next-line no-console console.log(`prometheus scrape endpoint: http://localhost:${port}${endpoint}`); }), - instrumentations: [], + instrumentations: [ + new RuntimeNodeInstrumentation({ + monitoringPrecision: 5000, + }), + ], serviceName: "demo-site", }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d4914b502..e971a2f272 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -664,6 +664,9 @@ importers: '@opentelemetry/exporter-trace-otlp-http': specifier: ^0.53.0 version: 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-runtime-node': + specifier: ^0.11.0 + version: 0.11.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-metrics': specifier: ^1.26.0 version: 1.26.0(@opentelemetry/api@1.9.0) @@ -7877,7 +7880,7 @@ packages: '@babel/helper-split-export-declaration': 7.18.6 '@babel/parser': 7.22.14 '@babel/types': 7.22.11 - debug: 4.3.4(supports-color@9.3.1) + debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -9936,7 +9939,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@9.3.1) + debug: 4.3.7 espree: 9.5.2 globals: 13.19.0 ignore: 5.2.4 @@ -12774,6 +12777,13 @@ packages: '@opentelemetry/api': 1.9.0 dev: false + /@opentelemetry/api-logs@0.56.0: + resolution: {integrity: sha512-Wr39+94UNNG3Ei9nv3pHd4AJ63gq5nSemMRpCd8fPwDL9rN3vK26lzxfH27mw16XzOSO+TpyQwBAMaLxaPWG0g==} + engines: {node: '>=14'} + dependencies: + '@opentelemetry/api': 1.9.0 + dev: false + /@opentelemetry/api@1.9.0: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -13447,6 +13457,18 @@ packages: - supports-color dev: false + /@opentelemetry/instrumentation-runtime-node@0.11.0(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-d7ZdzwnCpqaqvHkjowh8WA7/ZYr1jbGIo8QIpNPO+fqaxcm5NkzwP4kGpxI4PTnmeUTKcd6Bl/cPcKkR89u0ng==} + engines: {node: '>=17.4.0'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.56.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + dev: false + /@opentelemetry/instrumentation-socket.io@0.42.0(@opentelemetry/api@1.9.0): resolution: {integrity: sha512-xB5tdsBzuZyicQTO3hDzJIpHQ7V1BYJ6vWPWgl19gWZDBdjEGc3HOupjkd3BUJyDoDhbMEHGk2nNlkUU99EfkA==} engines: {node: '>=14'} @@ -13517,6 +13539,23 @@ packages: - supports-color dev: false + /@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-2KkGBKE+FPXU1F0zKww+stnlUxUTlBvLCiWdP63Z9sqXYeNI/ziNzsxAp4LAdUcTQmXjw1IWgvm5CAb/BHy99w==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.56.0 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.11.2 + require-in-the-middle: 7.4.0 + semver: 7.6.3 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + dev: false + /@opentelemetry/otlp-exporter-base@0.53.0(@opentelemetry/api@1.9.0): resolution: {integrity: sha512-UCWPreGQEhD6FjBaeDuXhiMf6kkBODF0ZQzrk/tuQcaVDJ+dDQ/xhJp192H9yWnKxVpEjFrSSLnpqmX4VwX+eA==} engines: {node: '>=14'} @@ -17084,7 +17123,6 @@ packages: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: acorn: 8.14.0 - dev: true /acorn-jsx@5.3.2(acorn@8.8.2): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -17097,7 +17135,7 @@ packages: resolution: {integrity: sha512-75lAs9H19ldmW+fAbyqHdjgdCrz0pWGXKmnqFoh8PyVd1L2RIb4RzYrSjmopeqv3E1G3/Pimu6GgLlrGbrkF7w==} engines: {node: '>=0.4.0'} dependencies: - acorn: 8.8.2 + acorn: 8.14.0 /acorn-walk@8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} @@ -18507,7 +18545,7 @@ packages: /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: - function-bind: 1.1.1 + function-bind: 1.1.2 get-intrinsic: 1.2.0 /call-bind@1.0.7: @@ -18814,12 +18852,8 @@ packages: engines: {node: '>=8'} dev: true - /cjs-module-lexer@1.2.2: - resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==} - /cjs-module-lexer@1.4.1: resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} - dev: true /class-transformer@0.5.1: resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} @@ -21132,7 +21166,7 @@ packages: call-bind: 1.0.2 es-set-tostringtag: 2.0.1 es-to-primitive: 1.2.1 - function-bind: 1.1.1 + function-bind: 1.1.2 function.prototype.name: 1.1.5 get-intrinsic: 1.2.0 get-symbol-description: 1.0.0 @@ -21442,8 +21476,8 @@ packages: resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} dependencies: debug: 3.2.7 - is-core-module: 2.11.0 - resolve: 1.22.1 + is-core-module: 2.15.0 + resolve: 1.22.8 transitivePeerDependencies: - supports-color dev: false @@ -22006,8 +22040,8 @@ packages: resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.8.2 - acorn-jsx: 5.3.2(acorn@8.8.2) + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 3.4.1 /espree@9.6.1: @@ -22744,7 +22778,7 @@ packages: minimatch: 3.1.2 node-abort-controller: 3.0.1 schema-utils: 3.1.1 - semver: 7.3.8 + semver: 7.6.3 tapable: 2.2.1 typescript: 4.9.4 webpack: 5.75.0 @@ -22923,9 +22957,6 @@ packages: rimraf: 2.7.1 dev: false - /function-bind@1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -23002,7 +23033,7 @@ packages: /get-intrinsic@1.2.0: resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} dependencies: - function-bind: 1.1.1 + function-bind: 1.1.2 has: 1.0.3 has-symbols: 1.0.3 @@ -23608,7 +23639,7 @@ packages: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} dependencies: - function-bind: 1.1.1 + function-bind: 1.1.2 /hasha@5.2.2: resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} @@ -24054,7 +24085,7 @@ packages: dependencies: acorn: 8.14.0 acorn-import-attributes: 1.9.5(acorn@8.14.0) - cjs-module-lexer: 1.2.2 + cjs-module-lexer: 1.4.1 module-details-from-path: 1.0.3 dev: false @@ -25261,7 +25292,7 @@ packages: '@jest/types': 29.5.0 '@types/node': 22.9.0 chalk: 4.1.2 - cjs-module-lexer: 1.2.2 + cjs-module-lexer: 1.4.1 collect-v8-coverage: 1.0.1 glob: 7.2.3 graceful-fs: 4.2.11 @@ -29492,7 +29523,7 @@ packages: dependencies: fill-keys: 1.0.2 module-not-found-error: 1.0.1 - resolve: 1.22.1 + resolve: 1.22.8 dev: false /prr@1.0.1: @@ -30839,7 +30870,7 @@ packages: resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} hasBin: true dependencies: - is-core-module: 2.11.0 + is-core-module: 2.15.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: false @@ -30856,7 +30887,7 @@ packages: resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} hasBin: true dependencies: - is-core-module: 2.11.0 + is-core-module: 2.15.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: false From 717015cf303d5ece4f14e2c2f337bac76e73e002 Mon Sep 17 00:00:00 2001 From: Alexander Kaufmann Date: Tue, 17 Dec 2024 15:19:36 +0100 Subject: [PATCH 013/266] Invoke SonarQube via GitHub action (#2937) Co-authored-by: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> --- .github/workflows/sonar-qube.yml | 38 ++++++++++++++++++++++++++++++++ sonar-project.properties | 8 +++++++ 2 files changed, 46 insertions(+) create mode 100644 .github/workflows/sonar-qube.yml create mode 100644 sonar-project.properties diff --git a/.github/workflows/sonar-qube.yml b/.github/workflows/sonar-qube.yml new file mode 100644 index 0000000000..c17cd85013 --- /dev/null +++ b/.github/workflows/sonar-qube.yml @@ -0,0 +1,38 @@ +name: SonarCloud Code Analysis + +on: + pull_request: + types: + - opened + - synchronize + - reopened + push: + branches: + - main + - next + +jobs: + sonarqube: + name: SonarQube + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: pnpm/action-setup@v4 + + - name: Use Node.js 22.x + uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: "https://registry.npmjs.org" + cache: "pnpm" + + - run: pnpm install --frozen-lockfile + + - name: SonarQube Scan + uses: SonarSource/sonarqube-scan-action@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000000..75c051fbf5 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,8 @@ +sonar.projectKey=vivid-planet_comet +sonar.organization=vivid-planet +sonar.sources=demo/,docs/,packages/,storybook/ +sonar.tests=packages/ +sonar.test.inclusions=packages/**/__tests__/**,packages/**/*.spec.ts +sonar.exclusions=packages/**/__tests__/**,packages/**/*.spec.ts,storybook/**/*.stories.tsx,storybook/**/mockServiceWorker.js,storybook/**/mocks/** +sonar.cpd.exclusions=packages/**/__tests__/**,packages/**/*.spec.ts,storybook/**/*.stories.tsx,storybook/**/mockServiceWorker.js,storybook/**/mocks/** +sonar.typescript.tsconfigPaths=demo/*/tsconfig.json,docs/tsconfig.json,packages/*/tsconfig.json,packages/*/*/tsconfig.json,storybook/tsconfig.json From a62cedf19ea9f1db3aa6d12fc69247352c1fc12f Mon Sep 17 00:00:00 2001 From: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> Date: Wed, 18 Dec 2024 09:33:56 +0100 Subject: [PATCH 014/266] SonarQube: Exclude Pages Router Demo site from duplication analysis (#2939) --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 75c051fbf5..f34858fd57 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -4,5 +4,5 @@ sonar.sources=demo/,docs/,packages/,storybook/ sonar.tests=packages/ sonar.test.inclusions=packages/**/__tests__/**,packages/**/*.spec.ts sonar.exclusions=packages/**/__tests__/**,packages/**/*.spec.ts,storybook/**/*.stories.tsx,storybook/**/mockServiceWorker.js,storybook/**/mocks/** -sonar.cpd.exclusions=packages/**/__tests__/**,packages/**/*.spec.ts,storybook/**/*.stories.tsx,storybook/**/mockServiceWorker.js,storybook/**/mocks/** +sonar.cpd.exclusions=demo/site-pages/**,packages/**/__tests__/**,packages/**/*.spec.ts,storybook/**/*.stories.tsx,storybook/**/mockServiceWorker.js,storybook/**/mocks/** sonar.typescript.tsconfigPaths=demo/*/tsconfig.json,docs/tsconfig.json,packages/*/tsconfig.json,packages/*/*/tsconfig.json,storybook/tsconfig.json From 4b48e6c2be15a279c1949988c7270f42cb9be96f Mon Sep 17 00:00:00 2001 From: "Piotr G." Date: Wed, 18 Dec 2024 09:58:45 +0100 Subject: [PATCH 015/266] Update Demo to latest Comet Starter (#2899) Co-authored-by: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> --- demo/admin/src/App.tsx | 158 +- .../admin/src/common/ContentScopeProvider.tsx | 17 +- demo/admin/src/common/MasterMenu.tsx | 56 +- .../src/common/blocks/AccordionBlock.tsx | 11 + .../src/common/blocks/AccordionItemBlock.tsx | 68 + .../src/common/blocks/CallToActionBlock.tsx | 33 + .../common/blocks/CallToActionListBlock.tsx | 11 + demo/admin/src/common/blocks/HeadingBlock.tsx | 93 + .../admin/src/common/blocks/HeadlineBlock.tsx | 50 - .../{pages => common}/blocks/LayoutBlock.tsx | 3 +- .../admin/src/common/blocks/LinkListBlock.tsx | 3 +- demo/admin/src/common/blocks/MediaBlock.tsx | 12 + .../src/common/blocks/MediaGalleryBlock.tsx | 40 + .../common/blocks/MediaGalleryItemBlock.tsx | 29 + .../admin/src/common/blocks/RichTextBlock.tsx | 20 +- demo/admin/src/common/blocks/SeoBlock.tsx | 3 - demo/admin/src/common/blocks/SpaceBlock.tsx | 25 +- .../StandaloneCallToActionListBlock.tsx | 35 + .../common/blocks/StandaloneHeadingBlock.tsx | 31 + .../common/blocks/StandaloneMediaBlock.tsx | 28 + .../admin/src/common/blocks/TextLinkBlock.tsx | 2 +- demo/admin/src/config.tsx | 6 +- .../{Dashboard.tsx => DashboardPage.tsx} | 7 +- .../src/{ => documents}/links/EditLink.tsx | 0 demo/admin/src/{ => documents}/links/Link.tsx | 3 +- .../src/{ => documents}/pages/EditPage.tsx | 4 +- demo/admin/src/{ => documents}/pages/Page.tsx | 4 +- .../pages/blocks/BasicStageBlock.tsx | 65 + .../pages/blocks/BillboardTeaserBlock.tsx | 59 + .../documents/pages/blocks/ColumnsBlock.tsx | 133 ++ .../pages/blocks/ContentGroupBlock.tsx | 64 + .../pages/blocks/FullWidthImageBlock.tsx | 0 .../pages/blocks/ImageLinkBlock.tsx | 0 .../documents/pages/blocks/KeyFactsBlock.tsx | 12 + .../pages/blocks/KeyFactsItemBlock.tsx | 45 + .../pages/blocks/MediaBlock.tsx | 0 .../pages/blocks}/PageContentBlock.tsx | 48 +- .../src/documents/pages/blocks/SeoBlock.tsx | 3 + .../src/documents/pages/blocks/StageBlock.tsx | 14 + .../documents/pages/blocks/TeaserBlock.tsx | 12 + .../pages/blocks/TeaserItemBlock.tsx | 45 + demo/admin/src/environment.ts | 2 +- demo/admin/src/footer/EditFooterPage.tsx | 191 ++ .../src/footer/blocks/FooterContentBlock.tsx | 35 + demo/admin/src/loader.ts | 9 +- .../src/{pages => }/mainMenu/MainMenu.tsx | 0 .../mainMenu/components/EditMainMenuItem.tsx | 0 .../mainMenu/components/MainMenuItems.tsx | 0 .../src/news/blocks/NewsContentBlock.tsx | 6 +- demo/admin/src/pages/blocks/ColumnsBlock.tsx | 80 - demo/admin/src/pages/blocks/TeaserBlock.tsx | 47 - demo/admin/src/pages/blocks/TwoListsBlock.tsx | 29 - demo/admin/src/util/mediaAspectRatios.ts | 19 + demo/api/block-meta.json | 1793 ++++++++++++----- demo/api/package.json | 2 +- demo/api/schema.gql | 18 +- demo/api/src/app.module.ts | 8 +- demo/api/src/comet-config.json | 10 +- .../src/common/blocks/accordion-item.block.ts | 61 + demo/api/src/common/blocks/accordion.block.ts | 4 + .../blocks/call-to-action-list.block.ts | 4 + .../src/common/blocks/call-to-action.block.ts | 43 + demo/api/src/common/blocks/heading.block.ts | 52 + .../blocks/{linkBlock => }/link.block.ts | 6 +- .../common/blocks/media-gallery-item.block.ts | 38 + .../src/common/blocks/media-gallery.block.ts | 40 + .../{pages => common}/blocks/media.block.ts | 7 +- demo/api/src/common/blocks/rich-text.block.ts | 3 +- demo/api/src/common/blocks/space.block.ts | 22 +- .../standalone-call-to-action-list.block.ts | 44 + .../common/blocks/standalone-heading.block.ts | 43 + .../common/blocks/standalone-media.block.ts | 39 + demo/api/src/common/blocks/text-link.block.ts | 5 +- demo/api/src/db/fixtures/fixtures.console.ts | 10 +- demo/api/src/db/fixtures/fixtures.module.ts | 4 +- .../generators/blocks/blocks.generator.ts | 4 +- .../generators/blocks/seo.generator.ts | 2 +- .../generators/blocks/space.generator.ts | 2 +- .../generators/blocks/text-image.generator.ts | 2 +- .../db/fixtures/generators/links.generator.ts | 4 +- .../many-images-test-page-fixture.service.ts | 7 +- .../db/migrations/Migration20241209092824.ts | 8 + .../{ => documents}/links/dto/link.input.ts | 2 +- .../links/entities/link.entity.ts | 2 +- .../src/{ => documents}/links/links.module.ts | 2 +- .../{ => documents}/links/links.resolver.ts | 0 .../pages/blocks/TextImageBlock.ts | 0 .../pages/blocks/basic-stage.block.ts | 71 + .../pages/blocks/billboard-teaser.block.ts | 59 + .../documents/pages/blocks/columns.block.ts | 35 + .../pages/blocks/content-group.block.ts | 73 + .../pages/blocks/full-width-image.block.ts | 0 .../pages/blocks/image-link.block.ts | 2 +- .../pages/blocks/key-facts-item.block.ts | 50 + .../documents/pages/blocks/key-facts.block.ts | 5 + .../pages/blocks/layout.block.ts | 2 +- .../pages/blocks/page-content.block.ts | 32 +- .../{ => documents}/pages/blocks/seo.block.ts | 0 .../src/documents/pages/blocks/stage.block.ts | 6 + .../pages/blocks/teaser-item.block.ts | 50 + .../documents/pages/blocks/teaser.block.ts | 5 + .../{ => documents}/pages/dto/page.input.ts | 6 + .../pages/entities/page.entity.ts | 6 + .../src/{ => documents}/pages/pages.module.ts | 0 .../{ => documents}/pages/pages.resolver.ts | 1 + .../src/footer/blocks/footer-content.block.ts | 62 +- .../footer-scope.ts} | 4 +- demo/api/src/footer/entities/footer.entity.ts | 23 +- demo/api/src/footer/footer.module.ts | 6 +- .../src/footer/generated/footer.resolver.ts | 6 +- demo/api/src/menus/menus.module.ts | 2 +- .../api/src/news/blocks/news-content.block.ts | 8 +- demo/api/src/pages/blocks/columns.block.ts | 29 - demo/api/src/pages/blocks/headline.block.ts | 54 - demo/api/src/pages/blocks/teaser.block.ts | 58 - demo/api/src/pages/blocks/two-lists.block.ts | 38 - demo/api/src/util/mediaAspectRatios.ts | 13 + demo/site-pages/package.json | 2 +- demo/site-pages/src/blocks/ColumnsBlock.tsx | 8 +- .../src/blocks/FullWidthImageBlock.tsx | 2 +- demo/site-pages/src/blocks/HeadingBlock.tsx | 79 + demo/site-pages/src/blocks/HeadlineBlock.tsx | 45 - demo/site-pages/src/blocks/MediaBlock.tsx | 4 +- .../src/blocks/PageContentBlock.tsx | 12 +- .../site-pages/src/blocks/RichTextBlock.sc.ts | 24 - demo/site-pages/src/blocks/RichTextBlock.tsx | 147 +- demo/site-pages/src/blocks/SpaceBlock.tsx | 4 +- demo/site-pages/src/blocks/TextImageBlock.tsx | 2 +- demo/site-pages/src/blocks/TextLinkBlock.tsx | 4 +- demo/site-pages/src/blocks/TwoListsBlock.tsx | 19 - .../components/common/HiddenIfInvalidLink.tsx | 35 + .../src/components/common/Typography.tsx | 214 ++ .../documents/pages/blocks/TeaserBlock.tsx | 31 - demo/site-pages/src/layout/PageLayout.tsx | 23 + .../src/pages/block-preview/main-menu.tsx | 2 +- demo/site-pages/src/redraft.d.ts | 9 +- demo/site-pages/src/theme.ts | 162 +- demo/site/package.json | 4 +- .../[domain]/[language]/[[...path]]/page.tsx | 2 +- .../[language]/news/[slug]/content.tsx | 2 +- .../[domain]/[language]/footer/page.tsx | 46 + .../[domain]/[language]/main-menu/page.tsx | 2 +- .../[domain]/[language]/page/page.tsx | 2 +- demo/site/src/blocks/ColumnsBlock.tsx | 51 - demo/site/src/blocks/HeadlineBlock.tsx | 46 - demo/site/src/blocks/MediaBlock.tsx | 29 - demo/site/src/blocks/RichTextBlock.tsx | 89 - demo/site/src/blocks/SpaceBlock.tsx | 22 - demo/site/src/blocks/TwoListsBlock.tsx | 20 - .../site/src/common/blocks/AccordionBlock.tsx | 35 + .../src/common/blocks/AccordionItemBlock.tsx | 103 + .../src/{ => common}/blocks/AnchorBlock.tsx | 10 +- .../src/common/blocks/CallToActionBlock.tsx | 24 + .../common/blocks/CallToActionListBlock.tsx | 28 + .../blocks/CookieSafeYouTubeVideoBlock.tsx | 3 +- .../src/{ => common}/blocks/DamImageBlock.tsx | 28 +- .../blocks/FullWidthImageBlock.tsx | 2 +- demo/site/src/common/blocks/HeadingBlock.tsx | 79 + .../{ => common}/blocks/InternalLinkBlock.tsx | 0 .../src/{ => common}/blocks/LayoutBlock.tsx | 21 +- .../src/{ => common}/blocks/LinkBlock.tsx | 0 .../src/{ => common}/blocks/LinkListBlock.tsx | 0 demo/site/src/common/blocks/MediaBlock.tsx | 29 + .../{ => common}/blocks/PageContentBlock.tsx | 20 +- demo/site/src/common/blocks/RichTextBlock.tsx | 153 ++ demo/site/src/common/blocks/SpaceBlock.tsx | 15 + .../StandaloneCallToActionListBlock.tsx | 44 + .../common/blocks/StandaloneHeadingBlock.tsx | 37 + .../common/blocks/StandaloneMediaBlock.tsx | 17 + .../{ => common}/blocks/TextImageBlock.tsx | 2 +- .../src/{ => common}/blocks/TextLinkBlock.tsx | 5 +- .../components/Breadcrumbs.fragment.ts | 0 .../{ => common}/components/Breadcrumbs.sc.ts | 5 + .../{ => common}/components/Breadcrumbs.tsx | 2 +- demo/site/src/common/components/Button.tsx | 87 + .../site/src/common/components/Typography.tsx | 214 ++ .../helpers}/CookiePlaceholders.tsx | 0 .../common/helpers/HiddenIfInvalidLink.tsx | 35 + demo/site/src/common/helpers/SvgUse.tsx | 12 + demo/site/src/components/common/GridRoot.tsx | 7 - .../{documentTypes => documents}/index.tsx | 5 +- .../links}/Link.tsx | 0 .../pages}/Page.tsx | 14 +- .../documents/pages/blocks/ImageLinkBlock.tsx | 4 +- .../documents/pages/blocks/TeaserBlock.tsx | 53 +- .../pages/blocks/TeaserItemBlock.tsx | 98 + demo/site/src/layout/PageLayout.tsx | 23 + .../site/src/layout/footer/Footer.fragment.ts | 7 + demo/site/src/layout/footer/Footer.tsx | 14 + .../footer/blocks/FooterContentBlock.tsx | 154 ++ .../{ => layout}/header/Header.fragment.ts | 0 demo/site/src/{ => layout}/header/Header.tsx | 0 .../{ => layout}/header/PageLink.fragment.ts | 0 .../site/src/{ => layout}/header/PageLink.tsx | 2 +- .../topNavigation/TopNavigation.fragment.ts | 0 .../topNavigation/TopNavigation.tsx | 2 +- demo/site/src/news/NewsList.tsx | 2 +- .../site/src/news/blocks/NewsContentBlock.tsx | 12 +- demo/site/src/redraft.d.ts | 9 +- demo/site/src/theme.ts | 162 +- .../src/util/StyledComponentsRegistry.tsx | 2 +- demo/site/src/util/spacing.ts | 220 -- docs/docs/blocks/block-factories.mdx | 12 +- docs/docs/blocks/your-first-block.mdx | 8 +- pnpm-lock.yaml | 28 +- 205 files changed, 5489 insertions(+), 2169 deletions(-) create mode 100644 demo/admin/src/common/blocks/AccordionBlock.tsx create mode 100644 demo/admin/src/common/blocks/AccordionItemBlock.tsx create mode 100644 demo/admin/src/common/blocks/CallToActionBlock.tsx create mode 100644 demo/admin/src/common/blocks/CallToActionListBlock.tsx create mode 100644 demo/admin/src/common/blocks/HeadingBlock.tsx delete mode 100644 demo/admin/src/common/blocks/HeadlineBlock.tsx rename demo/admin/src/{pages => common}/blocks/LayoutBlock.tsx (99%) create mode 100644 demo/admin/src/common/blocks/MediaBlock.tsx create mode 100644 demo/admin/src/common/blocks/MediaGalleryBlock.tsx create mode 100644 demo/admin/src/common/blocks/MediaGalleryItemBlock.tsx delete mode 100644 demo/admin/src/common/blocks/SeoBlock.tsx create mode 100644 demo/admin/src/common/blocks/StandaloneCallToActionListBlock.tsx create mode 100644 demo/admin/src/common/blocks/StandaloneHeadingBlock.tsx create mode 100644 demo/admin/src/common/blocks/StandaloneMediaBlock.tsx rename demo/admin/src/dashboard/{Dashboard.tsx => DashboardPage.tsx} (95%) rename demo/admin/src/{ => documents}/links/EditLink.tsx (100%) rename demo/admin/src/{ => documents}/links/Link.tsx (98%) rename demo/admin/src/{ => documents}/pages/EditPage.tsx (98%) rename demo/admin/src/{ => documents}/pages/Page.tsx (95%) create mode 100644 demo/admin/src/documents/pages/blocks/BasicStageBlock.tsx create mode 100644 demo/admin/src/documents/pages/blocks/BillboardTeaserBlock.tsx create mode 100644 demo/admin/src/documents/pages/blocks/ColumnsBlock.tsx create mode 100644 demo/admin/src/documents/pages/blocks/ContentGroupBlock.tsx rename demo/admin/src/{ => documents}/pages/blocks/FullWidthImageBlock.tsx (100%) rename demo/admin/src/{ => documents}/pages/blocks/ImageLinkBlock.tsx (100%) create mode 100644 demo/admin/src/documents/pages/blocks/KeyFactsBlock.tsx create mode 100644 demo/admin/src/documents/pages/blocks/KeyFactsItemBlock.tsx rename demo/admin/src/{ => documents}/pages/blocks/MediaBlock.tsx (100%) rename demo/admin/src/{pages => documents/pages/blocks}/PageContentBlock.tsx (58%) create mode 100644 demo/admin/src/documents/pages/blocks/SeoBlock.tsx create mode 100644 demo/admin/src/documents/pages/blocks/StageBlock.tsx create mode 100644 demo/admin/src/documents/pages/blocks/TeaserBlock.tsx create mode 100644 demo/admin/src/documents/pages/blocks/TeaserItemBlock.tsx create mode 100644 demo/admin/src/footer/EditFooterPage.tsx create mode 100644 demo/admin/src/footer/blocks/FooterContentBlock.tsx rename demo/admin/src/{pages => }/mainMenu/MainMenu.tsx (100%) rename demo/admin/src/{pages => }/mainMenu/components/EditMainMenuItem.tsx (100%) rename demo/admin/src/{pages => }/mainMenu/components/MainMenuItems.tsx (100%) delete mode 100644 demo/admin/src/pages/blocks/ColumnsBlock.tsx delete mode 100644 demo/admin/src/pages/blocks/TeaserBlock.tsx delete mode 100644 demo/admin/src/pages/blocks/TwoListsBlock.tsx create mode 100644 demo/admin/src/util/mediaAspectRatios.ts create mode 100644 demo/api/src/common/blocks/accordion-item.block.ts create mode 100644 demo/api/src/common/blocks/accordion.block.ts create mode 100644 demo/api/src/common/blocks/call-to-action-list.block.ts create mode 100644 demo/api/src/common/blocks/call-to-action.block.ts create mode 100644 demo/api/src/common/blocks/heading.block.ts rename demo/api/src/common/blocks/{linkBlock => }/link.block.ts (100%) create mode 100644 demo/api/src/common/blocks/media-gallery-item.block.ts create mode 100644 demo/api/src/common/blocks/media-gallery.block.ts rename demo/api/src/{pages => common}/blocks/media.block.ts (53%) create mode 100644 demo/api/src/common/blocks/standalone-call-to-action-list.block.ts create mode 100644 demo/api/src/common/blocks/standalone-heading.block.ts create mode 100644 demo/api/src/common/blocks/standalone-media.block.ts create mode 100644 demo/api/src/db/migrations/Migration20241209092824.ts rename demo/api/src/{ => documents}/links/dto/link.input.ts (87%) rename demo/api/src/{ => documents}/links/entities/link.entity.ts (94%) rename demo/api/src/{ => documents}/links/links.module.ts (86%) rename demo/api/src/{ => documents}/links/links.resolver.ts (100%) rename demo/api/src/{ => documents}/pages/blocks/TextImageBlock.ts (100%) create mode 100644 demo/api/src/documents/pages/blocks/basic-stage.block.ts create mode 100644 demo/api/src/documents/pages/blocks/billboard-teaser.block.ts create mode 100644 demo/api/src/documents/pages/blocks/columns.block.ts create mode 100644 demo/api/src/documents/pages/blocks/content-group.block.ts rename demo/api/src/{ => documents}/pages/blocks/full-width-image.block.ts (100%) rename demo/api/src/{ => documents}/pages/blocks/image-link.block.ts (70%) create mode 100644 demo/api/src/documents/pages/blocks/key-facts-item.block.ts create mode 100644 demo/api/src/documents/pages/blocks/key-facts.block.ts rename demo/api/src/{ => documents}/pages/blocks/layout.block.ts (96%) rename demo/api/src/{ => documents}/pages/blocks/page-content.block.ts (80%) rename demo/api/src/{ => documents}/pages/blocks/seo.block.ts (100%) create mode 100644 demo/api/src/documents/pages/blocks/stage.block.ts create mode 100644 demo/api/src/documents/pages/blocks/teaser-item.block.ts create mode 100644 demo/api/src/documents/pages/blocks/teaser.block.ts rename demo/api/src/{ => documents}/pages/dto/page.input.ts (76%) rename demo/api/src/{ => documents}/pages/entities/page.entity.ts (88%) rename demo/api/src/{ => documents}/pages/pages.module.ts (100%) rename demo/api/src/{ => documents}/pages/pages.resolver.ts (98%) rename demo/api/src/footer/{entities/footer-content-scope.entity.ts => dto/footer-scope.ts} (83%) delete mode 100644 demo/api/src/pages/blocks/columns.block.ts delete mode 100644 demo/api/src/pages/blocks/headline.block.ts delete mode 100644 demo/api/src/pages/blocks/teaser.block.ts delete mode 100644 demo/api/src/pages/blocks/two-lists.block.ts create mode 100644 demo/api/src/util/mediaAspectRatios.ts create mode 100644 demo/site-pages/src/blocks/HeadingBlock.tsx delete mode 100644 demo/site-pages/src/blocks/HeadlineBlock.tsx delete mode 100644 demo/site-pages/src/blocks/RichTextBlock.sc.ts delete mode 100644 demo/site-pages/src/blocks/TwoListsBlock.tsx create mode 100644 demo/site-pages/src/components/common/HiddenIfInvalidLink.tsx create mode 100644 demo/site-pages/src/components/common/Typography.tsx delete mode 100644 demo/site-pages/src/documents/pages/blocks/TeaserBlock.tsx create mode 100644 demo/site-pages/src/layout/PageLayout.tsx create mode 100644 demo/site/src/app/block-preview/[domain]/[language]/footer/page.tsx delete mode 100644 demo/site/src/blocks/ColumnsBlock.tsx delete mode 100644 demo/site/src/blocks/HeadlineBlock.tsx delete mode 100644 demo/site/src/blocks/MediaBlock.tsx delete mode 100644 demo/site/src/blocks/RichTextBlock.tsx delete mode 100644 demo/site/src/blocks/SpaceBlock.tsx delete mode 100644 demo/site/src/blocks/TwoListsBlock.tsx create mode 100644 demo/site/src/common/blocks/AccordionBlock.tsx create mode 100644 demo/site/src/common/blocks/AccordionItemBlock.tsx rename demo/site/src/{ => common}/blocks/AnchorBlock.tsx (58%) create mode 100644 demo/site/src/common/blocks/CallToActionBlock.tsx create mode 100644 demo/site/src/common/blocks/CallToActionListBlock.tsx rename demo/site/src/{ => common}/blocks/CookieSafeYouTubeVideoBlock.tsx (95%) rename demo/site/src/{ => common}/blocks/DamImageBlock.tsx (51%) rename demo/site/src/{ => common}/blocks/FullWidthImageBlock.tsx (95%) create mode 100644 demo/site/src/common/blocks/HeadingBlock.tsx rename demo/site/src/{ => common}/blocks/InternalLinkBlock.tsx (100%) rename demo/site/src/{ => common}/blocks/LayoutBlock.tsx (92%) rename demo/site/src/{ => common}/blocks/LinkBlock.tsx (100%) rename demo/site/src/{ => common}/blocks/LinkListBlock.tsx (100%) create mode 100644 demo/site/src/common/blocks/MediaBlock.tsx rename demo/site/src/{ => common}/blocks/PageContentBlock.tsx (67%) create mode 100644 demo/site/src/common/blocks/RichTextBlock.tsx create mode 100644 demo/site/src/common/blocks/SpaceBlock.tsx create mode 100644 demo/site/src/common/blocks/StandaloneCallToActionListBlock.tsx create mode 100644 demo/site/src/common/blocks/StandaloneHeadingBlock.tsx create mode 100644 demo/site/src/common/blocks/StandaloneMediaBlock.tsx rename demo/site/src/{ => common}/blocks/TextImageBlock.tsx (95%) rename demo/site/src/{ => common}/blocks/TextLinkBlock.tsx (85%) rename demo/site/src/{ => common}/components/Breadcrumbs.fragment.ts (100%) rename demo/site/src/{ => common}/components/Breadcrumbs.sc.ts (84%) rename demo/site/src/{ => common}/components/Breadcrumbs.tsx (94%) create mode 100644 demo/site/src/common/components/Button.tsx create mode 100644 demo/site/src/common/components/Typography.tsx rename demo/site/src/{components/common => common/helpers}/CookiePlaceholders.tsx (100%) create mode 100644 demo/site/src/common/helpers/HiddenIfInvalidLink.tsx create mode 100644 demo/site/src/common/helpers/SvgUse.tsx delete mode 100644 demo/site/src/components/common/GridRoot.tsx rename demo/site/src/{documentTypes => documents}/index.tsx (88%) rename demo/site/src/{documentTypes => documents/links}/Link.tsx (100%) rename demo/site/src/{documentTypes => documents/pages}/Page.tsx (91%) create mode 100644 demo/site/src/documents/pages/blocks/TeaserItemBlock.tsx create mode 100644 demo/site/src/layout/PageLayout.tsx create mode 100644 demo/site/src/layout/footer/Footer.fragment.ts create mode 100644 demo/site/src/layout/footer/Footer.tsx create mode 100644 demo/site/src/layout/footer/blocks/FooterContentBlock.tsx rename demo/site/src/{ => layout}/header/Header.fragment.ts (100%) rename demo/site/src/{ => layout}/header/Header.tsx (100%) rename demo/site/src/{ => layout}/header/PageLink.fragment.ts (100%) rename demo/site/src/{ => layout}/header/PageLink.tsx (96%) rename demo/site/src/{ => layout}/topNavigation/TopNavigation.fragment.ts (100%) rename demo/site/src/{ => layout}/topNavigation/TopNavigation.tsx (97%) delete mode 100644 demo/site/src/util/spacing.ts diff --git a/demo/admin/src/App.tsx b/demo/admin/src/App.tsx index 07707bdc75..3a6e2c72e8 100644 --- a/demo/admin/src/App.tsx +++ b/demo/admin/src/App.tsx @@ -1,13 +1,11 @@ import "@fontsource-variable/roboto-flex/full.css"; -import "material-design-icons/iconfont/material-icons.css"; -import "typeface-open-sans"; import "@src/polyfills"; import { ApolloProvider } from "@apollo/client"; import { ErrorDialogHandler, MasterLayout, MuiThemeProvider, RouterBrowserRouter, SnackbarProvider } from "@comet/admin"; import { + BuildInformationProvider, CmsBlockContextProvider, - ContentScopeInterface, createDamFileDependency, createHttpClient, CurrentUserProvider, @@ -20,25 +18,20 @@ import { } from "@comet/cms-admin"; import { css, Global } from "@emotion/react"; import { createApolloClient } from "@src/common/apollo/createApolloClient"; -import ContentScopeProvider, { ContentScope } from "@src/common/ContentScopeProvider"; -import { additionalPageTreeNodeFieldsFragment } from "@src/common/EditPageNode"; import { ConfigProvider, createConfig } from "@src/config"; -import { pageTreeCategories } from "@src/pageTree/pageTreeCategories"; import { theme } from "@src/theme"; import { HTML5toTouch } from "rdndmb-html5-to-touch"; -import { Component, Fragment } from "react"; import { DndProvider } from "react-dnd-multi-backend"; -import * as ReactDOM from "react-dom"; import { FormattedMessage, IntlProvider } from "react-intl"; -import { Route, Switch } from "react-router-dom"; +import { Route, Switch } from "react-router"; +import { ContentScopeProvider } from "./common/ContentScopeProvider"; import MasterHeader from "./common/MasterHeader"; -import MasterMenu, { masterMenuData, pageTreeDocumentTypes } from "./common/MasterMenu"; +import { AppMasterMenu, masterMenuData, pageTreeCategories, pageTreeDocumentTypes } from "./common/MasterMenu"; import { ImportFromPicsum } from "./dam/ImportFromPicsum"; +import { Link } from "./documents/links/Link"; +import { Page } from "./documents/pages/Page"; import { getMessages } from "./lang"; -import { Link } from "./links/Link"; -import { NewsDependency } from "./news/dependencies/NewsDependency"; -import { Page } from "./pages/Page"; const GlobalStyle = () => ( ( `} /> ); - const config = createConfig(); const apolloClient = createApolloClient(config.apiUrl); const apiClient = createHttpClient(config.apiUrl); -class App extends Component { - public static render(baseEl: Element): void { - ReactDOM.render(, baseEl); - } - - public render(): JSX.Element { - return ( - - +export function App() { + return ( + + + { - const siteConfig = configs.find((config) => config.scope.domain === scope.domain); + const siteConfig = configs.find((config) => { + return config.scope.domain === scope.domain; + }); + if (!siteConfig) throw new Error(`siteConfig not found for domain ${scope.domain}`); return { url: siteConfig.url, @@ -100,78 +91,69 @@ class App extends Component { entityDependencyMap={{ Page, Link, - News: NewsDependency, DamFile: createDamFileDependency(), }} > - scope.domain}> + scope.domain}> - - - - - - - - - - {({ match }) => ( - - {/* @TODO: add preview to contentScope once site is capable of contentScope */} - ( - { - return `/${scope.language}${path}`; - }} - {...props} - /> - )} - /> - ( - - - - )} - /> - - )} - - - - - - - + + + + + + + + + {({ match }) => ( + + ( + { + return `/${scope.language}${path}`; + }} + {...props} + /> + )} + /> + ( + + + + )} + /> + + )} + + + + + + - - - ); - } + + + + ); } -export default App; diff --git a/demo/admin/src/common/ContentScopeProvider.tsx b/demo/admin/src/common/ContentScopeProvider.tsx index f64226f90b..c96ffbb480 100644 --- a/demo/admin/src/common/ContentScopeProvider.tsx +++ b/demo/admin/src/common/ContentScopeProvider.tsx @@ -8,28 +8,21 @@ import { useContentScopeConfig as useContentScopeConfigLibrary, useCurrentUser, } from "@comet/cms-admin"; +import { ContentScope } from "@src/site-configs"; -type Domain = "main" | "secondary" | string; -type Language = "en" | string; -export interface ContentScope { - domain: Domain; - language: Language; -} - -// convenince wrapper for app (Bind Generic) +// convenience wrapper for app (Bind Generic) export function useContentScope(): UseContentScopeApi { return useContentScopeLibrary(); } -// @TODO (maybe): make factory in library to statically create Provider - export function useContentScopeConfig(p: ContentScopeConfigProps): void { return useContentScopeConfigLibrary(p); } -const ContentScopeProvider = ({ children }: Pick) => { +export const ContentScopeProvider = ({ children }: Pick) => { const user = useCurrentUser(); + // TODO in COMET: filter already in API, avoid type cast, support labels const userContentScopes = user.allowedContentScopes.filter( (value, index, self) => self.map((x) => JSON.stringify(x)).indexOf(JSON.stringify(value)) == index, ) as ContentScope[]; @@ -45,5 +38,3 @@ const ContentScopeProvider = ({ children }: Pick ); }; - -export default ContentScopeProvider; diff --git a/demo/admin/src/common/MasterMenu.tsx b/demo/admin/src/common/MasterMenu.tsx index d1b64136d7..d8a038a590 100644 --- a/demo/admin/src/common/MasterMenu.tsx +++ b/demo/admin/src/common/MasterMenu.tsx @@ -1,60 +1,67 @@ -import { Assets, Dashboard as DashboardIcon, Data, PageTree, Snips, Wrench } from "@comet/admin-icons"; +import { Assets, Dashboard, Data, PageTree, Snips, Wrench } from "@comet/admin-icons"; import { + AllCategories, ContentScopeIndicator, createRedirectsPage, CronJobsPage, DamPage, DocumentInterface, DocumentType, - MasterMenu as CometMasterMenu, + MasterMenu, MasterMenuData, PagesPage, PublisherPage, UserPermissionsPage, } from "@comet/cms-admin"; -import { ContentScope } from "@src/common/ContentScopeProvider"; import { ImportFromPicsum } from "@src/dam/ImportFromPicsum"; -import Dashboard from "@src/dashboard/Dashboard"; +import { DashboardPage } from "@src/dashboard/DashboardPage"; +import { Link } from "@src/documents/links/Link"; +import { Page } from "@src/documents/pages/Page"; import { PredefinedPage } from "@src/documents/predefinedPages/PredefinedPage"; +import { EditFooterPage } from "@src/footer/EditFooterPage"; import { GQLPageTreeNodeCategory } from "@src/graphql.generated"; -import { Link } from "@src/links/Link"; +import MainMenu from "@src/mainMenu/MainMenu"; import { NewsLinkBlock } from "@src/news/blocks/NewsLinkBlock"; import { NewsPage } from "@src/news/NewsPage"; -import MainMenu from "@src/pages/mainMenu/MainMenu"; -import { Page } from "@src/pages/Page"; -import { categoryToUrlParam, pageTreeCategories, urlParamToCategory } from "@src/pageTree/pageTreeCategories"; +import { categoryToUrlParam, urlParamToCategory } from "@src/pageTree/pageTreeCategories"; import ProductCategoriesPage from "@src/products/categories/ProductCategoriesPage"; import { CombinationFieldsTestProductsPage } from "@src/products/future/CombinationFieldsTestProductsPage"; import { CreateCapProductPage as FutureCreateCapProductPage } from "@src/products/future/CreateCapProductPage"; import { ManufacturersPage as FutureManufacturersPage } from "@src/products/future/ManufacturersPage"; -import { ProductsPage as FutureProductsPage } from "@src/products/future/ProductsPage"; +import { ProductsPage as FutureProductsPage, ProductsPage } from "@src/products/future/ProductsPage"; import { ProductsWithLowPricePage as FutureProductsWithLowPricePage } from "@src/products/future/ProductsWithLowPricePage"; -import { ProductsPage } from "@src/products/generated/ProductsPage"; import { ManufacturersPage as ManufacturersHandmadePage } from "@src/products/ManufacturersPage"; import ProductsHandmadePage from "@src/products/ProductsPage"; import ProductTagsPage from "@src/products/tags/ProductTagsPage"; +import { ContentScope } from "@src/site-configs"; import { FormattedMessage } from "react-intl"; -import { Redirect, RouteComponentProps } from "react-router-dom"; +import { Redirect, RouteComponentProps } from "react-router"; import { ComponentDemo } from "./ComponentDemo"; import { EditPageNode } from "./EditPageNode"; -export const pageTreeDocumentTypes = { +export const pageTreeCategories: AllCategories = [ + { + category: "MainNavigation", + label: , + }, +]; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const pageTreeDocumentTypes: Record> = { Page, Link, - PredefinedPage, }; - const RedirectsPage = createRedirectsPage({ customTargets: { news: NewsLinkBlock }, scopeParts: ["domain"] }); export const masterMenuData: MasterMenuData = [ { type: "route", primary: , - icon: , + icon: , route: { path: "/dashboard", - component: Dashboard, + component: DashboardPage, }, }, { @@ -130,16 +137,26 @@ export const masterMenuData: MasterMenuData = [ }, { type: "collapsible", - primary: , + primary: , icon: , items: [ { type: "route", - primary: , + primary: , route: { path: "/project-snips/main-menu", component: MainMenu, }, + requiredPermission: "pageTree", + }, + { + type: "route", + primary: , + route: { + path: "/project-snips/footer", + component: EditFooterPage, + }, + requiredPermission: "pageTree", }, ], requiredPermission: "pageTree", @@ -309,5 +326,4 @@ export const masterMenuData: MasterMenuData = [ }, ]; -const MasterMenu = () => ; -export default MasterMenu; +export const AppMasterMenu = () => ; diff --git a/demo/admin/src/common/blocks/AccordionBlock.tsx b/demo/admin/src/common/blocks/AccordionBlock.tsx new file mode 100644 index 0000000000..c11e301c1b --- /dev/null +++ b/demo/admin/src/common/blocks/AccordionBlock.tsx @@ -0,0 +1,11 @@ +import { createListBlock } from "@comet/blocks-admin"; +import { AccordionItemBlock } from "@src/common/blocks/AccordionItemBlock"; +import { FormattedMessage } from "react-intl"; + +export const AccordionBlock = createListBlock({ + name: "Accordion", + displayName: , + block: AccordionItemBlock, + itemName: , + itemsName: , +}); diff --git a/demo/admin/src/common/blocks/AccordionItemBlock.tsx b/demo/admin/src/common/blocks/AccordionItemBlock.tsx new file mode 100644 index 0000000000..599ed5bf84 --- /dev/null +++ b/demo/admin/src/common/blocks/AccordionItemBlock.tsx @@ -0,0 +1,68 @@ +import { SwitchField } from "@comet/admin"; +import { + BlockCategory, + BlocksFinalForm, + createBlocksBlock, + createCompositeBlock, + createCompositeBlockTextField, + createCompositeSetting, +} from "@comet/blocks-admin"; +import { AccordionItemBlockData } from "@src/blocks.generated"; +import { RichTextBlock } from "@src/common/blocks/RichTextBlock"; +import { SpaceBlock } from "@src/common/blocks/SpaceBlock"; +import { StandaloneCallToActionListBlock } from "@src/common/blocks/StandaloneCallToActionListBlock"; +import { StandaloneHeadingBlock } from "@src/common/blocks/StandaloneHeadingBlock"; +import { FormattedMessage } from "react-intl"; + +const AccordionContentBlock = createBlocksBlock({ + name: "AccordionContent", + supportedBlocks: { + richText: RichTextBlock, + space: SpaceBlock, + heading: StandaloneHeadingBlock, + callToActionList: StandaloneCallToActionListBlock, + }, +}); + +export const AccordionItemBlock = createCompositeBlock( + { + name: "AccordionItem", + displayName: , + blocks: { + title: { + block: createCompositeBlockTextField({ + fieldProps: { fullWidth: true, label: }, + }), + hiddenInSubroute: true, + }, + content: { + block: AccordionContentBlock, + title: , + }, + openByDefault: { + block: createCompositeSetting({ + defaultValue: false, + AdminComponent: ({ state, updateState }) => { + return ( + + onSubmit={({ openByDefault }) => updateState(openByDefault)} + initialValues={{ openByDefault: state }} + > + } + /> + + ); + }, + }), + hiddenInSubroute: true, + }, + }, + }, + (block) => { + block.category = BlockCategory.TextAndContent; + block.previewContent = (state) => (state.title !== undefined ? [{ type: "text", content: state.title }] : []); + return block; + }, +); diff --git a/demo/admin/src/common/blocks/CallToActionBlock.tsx b/demo/admin/src/common/blocks/CallToActionBlock.tsx new file mode 100644 index 0000000000..24892b9777 --- /dev/null +++ b/demo/admin/src/common/blocks/CallToActionBlock.tsx @@ -0,0 +1,33 @@ +import { BlockCategory, createCompositeBlock, createCompositeBlockSelectField } from "@comet/blocks-admin"; +import { CallToActionBlockData } from "@src/blocks.generated"; +import { FormattedMessage } from "react-intl"; + +import { TextLinkBlock } from "./TextLinkBlock"; + +export const CallToActionBlock = createCompositeBlock( + { + name: "CallToAction", + displayName: , + blocks: { + textLink: { + block: TextLinkBlock, + title: , + }, + variant: { + block: createCompositeBlockSelectField({ + defaultValue: "Contained", + options: [ + { value: "Contained", label: }, + { value: "Outlined", label: }, + { value: "Text", label: }, + ], + fieldProps: { label: , fullWidth: true }, + }), + }, + }, + }, + (block) => { + block.category = BlockCategory.Navigation; + return block; + }, +); diff --git a/demo/admin/src/common/blocks/CallToActionListBlock.tsx b/demo/admin/src/common/blocks/CallToActionListBlock.tsx new file mode 100644 index 0000000000..912e32aed4 --- /dev/null +++ b/demo/admin/src/common/blocks/CallToActionListBlock.tsx @@ -0,0 +1,11 @@ +import { createListBlock } from "@comet/blocks-admin"; +import { CallToActionBlock } from "@src/common/blocks/CallToActionBlock"; +import { FormattedMessage } from "react-intl"; + +export const CallToActionListBlock = createListBlock({ + name: "CallToActionList", + displayName: , + block: CallToActionBlock, + itemName: , + itemsName: , +}); diff --git a/demo/admin/src/common/blocks/HeadingBlock.tsx b/demo/admin/src/common/blocks/HeadingBlock.tsx new file mode 100644 index 0000000000..830e6b65e6 --- /dev/null +++ b/demo/admin/src/common/blocks/HeadingBlock.tsx @@ -0,0 +1,93 @@ +import { BlockCategory, createCompositeBlock, createCompositeBlockSelectField } from "@comet/blocks-admin"; +import { createRichTextBlock } from "@comet/cms-admin"; +import { HeadingBlockData } from "@src/blocks.generated"; +import { FormattedMessage } from "react-intl"; + +import { LinkBlock } from "./LinkBlock"; + +const EyebrowRichTextBlock = createRichTextBlock({ + link: LinkBlock, + rte: { + maxBlocks: 1, + supports: ["bold", "italic", "sub", "sup", "non-breaking-space", "soft-hyphen"], + }, + minHeight: 0, +}); + +const HeadlineRichTextBlock = createRichTextBlock({ + link: LinkBlock, + rte: { + maxBlocks: 1, + supports: [ + "header-one", + "header-two", + "header-three", + "header-four", + "header-five", + "header-six", + "bold", + "italic", + "sub", + "sup", + "non-breaking-space", + "soft-hyphen", + ], + standardBlockType: "header-one", + blocktypeMap: { + "header-one": { + label: , + }, + "header-two": { + label: , + }, + "header-three": { + label: , + }, + "header-four": { + label: , + }, + "header-five": { + label: , + }, + "header-six": { + label: , + }, + }, + }, + minHeight: 0, +}); + +export const HeadingBlock = createCompositeBlock( + { + name: "Heading", + displayName: , + blocks: { + eyebrow: { + block: EyebrowRichTextBlock, + title: , + }, + headline: { + block: HeadlineRichTextBlock, + title: , + }, + htmlTag: { + block: createCompositeBlockSelectField({ + defaultValue: "H2", + options: [ + { value: "H1", label: }, + { value: "H2", label: }, + { value: "H3", label: }, + { value: "H4", label: }, + { value: "H5", label: }, + { value: "H6", label: }, + ], + fieldProps: { label: , fullWidth: true }, + }), + }, + }, + }, + (block) => { + block.category = BlockCategory.TextAndContent; + return block; + }, +); diff --git a/demo/admin/src/common/blocks/HeadlineBlock.tsx b/demo/admin/src/common/blocks/HeadlineBlock.tsx deleted file mode 100644 index 6c93a3393a..0000000000 --- a/demo/admin/src/common/blocks/HeadlineBlock.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { BlockCategory, createCompositeBlock, createCompositeBlockSelectField, createCompositeBlockTextField } from "@comet/blocks-admin"; -import { createRichTextBlock } from "@comet/cms-admin"; -import { HeadlineBlockData } from "@src/blocks.generated"; -import { LinkBlock } from "@src/common/blocks/LinkBlock"; - -const RichTextBlock = createRichTextBlock({ - link: LinkBlock, - rte: { - maxBlocks: 1, - supports: ["bold", "italic", "non-breaking-space"], - blocktypeMap: {}, - }, - minHeight: 0, -}); - -export const HeadlineBlock = createCompositeBlock( - { - name: "Headline", - displayName: "Headline", - blocks: { - eyebrow: { - block: createCompositeBlockTextField({ - fieldProps: { label: "Eyebrow" }, - }), - }, - headline: { - block: RichTextBlock, - title: "Headline", - }, - level: { - block: createCompositeBlockSelectField({ - defaultValue: "header-one", - fieldProps: { label: "Level", fullWidth: true }, - options: [ - { value: "header-one", label: "Header One" }, - { value: "header-two", label: "Header Two" }, - { value: "header-three", label: "Header Three" }, - { value: "header-four", label: "Header Four" }, - { value: "header-five", label: "Header Five" }, - { value: "header-six", label: "Header Six" }, - ], - }), - }, - }, - }, - (block) => ({ - ...block, - category: BlockCategory.TextAndContent, - }), -); diff --git a/demo/admin/src/pages/blocks/LayoutBlock.tsx b/demo/admin/src/common/blocks/LayoutBlock.tsx similarity index 99% rename from demo/admin/src/pages/blocks/LayoutBlock.tsx rename to demo/admin/src/common/blocks/LayoutBlock.tsx index 7d167081f8..5195ba6364 100644 --- a/demo/admin/src/pages/blocks/LayoutBlock.tsx +++ b/demo/admin/src/common/blocks/LayoutBlock.tsx @@ -13,9 +13,10 @@ import { } from "@comet/blocks-admin"; import { LayoutBlockData } from "@src/blocks.generated"; import { RichTextBlock } from "@src/common/blocks/RichTextBlock"; -import { MediaBlock } from "@src/pages/blocks/MediaBlock"; import { FormattedMessage } from "react-intl"; +import { MediaBlock } from "./MediaBlock"; + const layoutOptions: (ColumnsBlockLayout & { visibleBlocks: string[] })[] = [ { name: "layout1", diff --git a/demo/admin/src/common/blocks/LinkListBlock.tsx b/demo/admin/src/common/blocks/LinkListBlock.tsx index 4432a8ceff..39cbc43933 100644 --- a/demo/admin/src/common/blocks/LinkListBlock.tsx +++ b/demo/admin/src/common/blocks/LinkListBlock.tsx @@ -1,11 +1,10 @@ import { createListBlock } from "@comet/blocks-admin"; +import { TextLinkBlock } from "@src/common/blocks/TextLinkBlock"; import { userGroupAdditionalItemFields } from "@src/userGroups/userGroupAdditionalItemFields"; import { UserGroupChip } from "@src/userGroups/UserGroupChip"; import { UserGroupContextMenuItem } from "@src/userGroups/UserGroupContextMenuItem"; import { FormattedMessage } from "react-intl"; -import { TextLinkBlock } from "./TextLinkBlock"; - export const LinkListBlock = createListBlock({ name: "LinkList", block: TextLinkBlock, diff --git a/demo/admin/src/common/blocks/MediaBlock.tsx b/demo/admin/src/common/blocks/MediaBlock.tsx new file mode 100644 index 0000000000..09cd689c0f --- /dev/null +++ b/demo/admin/src/common/blocks/MediaBlock.tsx @@ -0,0 +1,12 @@ +import { BlockCategory, BlockInterface, createOneOfBlock } from "@comet/blocks-admin"; +import { DamImageBlock, DamVideoBlock, YouTubeVideoBlock } from "@comet/cms-admin"; +import { FormattedMessage } from "react-intl"; + +export const MediaBlock: BlockInterface = createOneOfBlock({ + supportedBlocks: { image: DamImageBlock, damVideo: DamVideoBlock, youTubeVideo: YouTubeVideoBlock }, + name: "Media", + displayName: , + allowEmpty: false, + variant: "toggle", + category: BlockCategory.Media, +}); diff --git a/demo/admin/src/common/blocks/MediaGalleryBlock.tsx b/demo/admin/src/common/blocks/MediaGalleryBlock.tsx new file mode 100644 index 0000000000..b5d5f964f6 --- /dev/null +++ b/demo/admin/src/common/blocks/MediaGalleryBlock.tsx @@ -0,0 +1,40 @@ +import { BlockCategory, createCompositeBlock, createCompositeBlockSelectField, createListBlock } from "@comet/blocks-admin"; +import { MediaGalleryBlockData } from "@src/blocks.generated"; +import { MediaGalleryItemBlock } from "@src/common/blocks/MediaGalleryItemBlock"; +import { mediaAspectRatioOptions } from "@src/util/mediaAspectRatios"; +import { FormattedMessage } from "react-intl"; + +const MediaGalleryListBlock = createListBlock({ + name: "MediaGalleryList", + block: MediaGalleryItemBlock, + itemName: , + itemsName: , +}); + +export const MediaGalleryBlock = createCompositeBlock( + { + name: "MediaGallery", + displayName: , + blocks: { + items: { + block: MediaGalleryListBlock, + title: , + }, + aspectRatio: { + block: createCompositeBlockSelectField({ + defaultValue: "16x9", + options: mediaAspectRatioOptions, + fieldProps: { + label: , + fullWidth: true, + }, + }), + hiddenInSubroute: true, + }, + }, + }, + (block) => { + block.category = BlockCategory.Media; + return block; + }, +); diff --git a/demo/admin/src/common/blocks/MediaGalleryItemBlock.tsx b/demo/admin/src/common/blocks/MediaGalleryItemBlock.tsx new file mode 100644 index 0000000000..db94e9a18a --- /dev/null +++ b/demo/admin/src/common/blocks/MediaGalleryItemBlock.tsx @@ -0,0 +1,29 @@ +import { BlockCategory, createCompositeBlock, createCompositeBlockTextField } from "@comet/blocks-admin"; +import { MediaBlock } from "@src/common/blocks/MediaBlock"; +import { FormattedMessage } from "react-intl"; + +export const MediaGalleryItemBlock = createCompositeBlock( + { + name: "MediaGalleryItem", + displayName: , + blocks: { + media: { + block: MediaBlock, + title: , + }, + caption: { + block: createCompositeBlockTextField({ + fieldProps: { + fullWidth: true, + label: , + }, + }), + hiddenInSubroute: true, + }, + }, + }, + (block) => { + block.category = BlockCategory.Media; + return block; + }, +); diff --git a/demo/admin/src/common/blocks/RichTextBlock.tsx b/demo/admin/src/common/blocks/RichTextBlock.tsx index 3a8e878a09..96670b8bb9 100644 --- a/demo/admin/src/common/blocks/RichTextBlock.tsx +++ b/demo/admin/src/common/blocks/RichTextBlock.tsx @@ -1,5 +1,23 @@ import { createRichTextBlock } from "@comet/cms-admin"; +import { Typography } from "@mui/material"; +import { FormattedMessage } from "react-intl"; import { LinkBlock } from "./LinkBlock"; -export const RichTextBlock = createRichTextBlock({ link: LinkBlock }); +export const RichTextBlock = createRichTextBlock({ + link: LinkBlock, + rte: { + standardBlockType: "paragraph-standard", + blocktypeMap: { + "paragraph-standard": { + label: , + }, + "paragraph-small": { + label: , + renderConfig: { + element: (props) => , + }, + }, + }, + }, +}); diff --git a/demo/admin/src/common/blocks/SeoBlock.tsx b/demo/admin/src/common/blocks/SeoBlock.tsx deleted file mode 100644 index 6f5e3da078..0000000000 --- a/demo/admin/src/common/blocks/SeoBlock.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { createSeoBlock } from "@comet/cms-admin"; - -export const SeoBlock = createSeoBlock(); diff --git a/demo/admin/src/common/blocks/SpaceBlock.tsx b/demo/admin/src/common/blocks/SpaceBlock.tsx index 177fd4b1b6..0c98375e30 100644 --- a/demo/admin/src/common/blocks/SpaceBlock.tsx +++ b/demo/admin/src/common/blocks/SpaceBlock.tsx @@ -1,18 +1,19 @@ import { createSpaceBlock } from "@comet/blocks-admin"; +import { SpaceBlockData } from "@src/blocks.generated"; import { ReactNode } from "react"; import { FormattedMessage } from "react-intl"; -const options: { value: string; label: ReactNode }[] = [ - { value: "d150", label: }, - { value: "d200", label: }, - { value: "d250", label: }, - { value: "d300", label: }, - { value: "d350", label: }, - { value: "d400", label: }, - { value: "d450", label: }, - { value: "d500", label: }, - { value: "d550", label: }, - { value: "d600", label: }, +const options: { value: SpaceBlockData["spacing"]; label: ReactNode }[] = [ + { value: "D100", label: }, + { value: "D200", label: }, + { value: "D300", label: }, + { value: "D400", label: }, + { value: "S100", label: }, + { value: "S200", label: }, + { value: "S300", label: }, + { value: "S400", label: }, + { value: "S500", label: }, + { value: "S600", label: }, ]; -export const SpaceBlock = createSpaceBlock({ defaultValue: options[0].value, options }); +export const SpaceBlock = createSpaceBlock({ defaultValue: options[0].value, options }); diff --git a/demo/admin/src/common/blocks/StandaloneCallToActionListBlock.tsx b/demo/admin/src/common/blocks/StandaloneCallToActionListBlock.tsx new file mode 100644 index 0000000000..82274a9edc --- /dev/null +++ b/demo/admin/src/common/blocks/StandaloneCallToActionListBlock.tsx @@ -0,0 +1,35 @@ +import { BlockCategory, createCompositeBlock, createCompositeBlockSelectField } from "@comet/blocks-admin"; +import { StandaloneCallToActionListBlockData } from "@src/blocks.generated"; +import { CallToActionListBlock } from "@src/common/blocks/CallToActionListBlock"; +import { FormattedMessage } from "react-intl"; + +export const StandaloneCallToActionListBlock = createCompositeBlock( + { + name: "StandaloneCallToActionList", + displayName: , + blocks: { + callToActionList: { + block: CallToActionListBlock, + }, + alignment: { + block: createCompositeBlockSelectField({ + defaultValue: "left", + options: [ + { value: "left", label: }, + { value: "center", label: }, + { value: "right", label: }, + ], + fieldProps: { + label: , + fullWidth: true, + }, + }), + hiddenInSubroute: true, + }, + }, + }, + (block) => { + block.category = BlockCategory.Navigation; + return block; + }, +); diff --git a/demo/admin/src/common/blocks/StandaloneHeadingBlock.tsx b/demo/admin/src/common/blocks/StandaloneHeadingBlock.tsx new file mode 100644 index 0000000000..69b13ed4e0 --- /dev/null +++ b/demo/admin/src/common/blocks/StandaloneHeadingBlock.tsx @@ -0,0 +1,31 @@ +import { BlockCategory, createCompositeBlock, createCompositeBlockSelectField } from "@comet/blocks-admin"; +import { StandaloneHeadingBlockData } from "@src/blocks.generated"; +import { HeadingBlock } from "@src/common/blocks/HeadingBlock"; +import * as React from "react"; +import { FormattedMessage } from "react-intl"; + +export const StandaloneHeadingBlock = createCompositeBlock( + { + name: "StandaloneHeading", + displayName: , + blocks: { + heading: { + block: HeadingBlock, + }, + textAlignment: { + block: createCompositeBlockSelectField({ + defaultValue: "left", + options: [ + { value: "left", label: }, + { value: "center", label: }, + ], + fieldProps: { label: , fullWidth: true }, + }), + }, + }, + }, + (block) => { + block.category = BlockCategory.TextAndContent; + return block; + }, +); diff --git a/demo/admin/src/common/blocks/StandaloneMediaBlock.tsx b/demo/admin/src/common/blocks/StandaloneMediaBlock.tsx new file mode 100644 index 0000000000..c0b16b92c3 --- /dev/null +++ b/demo/admin/src/common/blocks/StandaloneMediaBlock.tsx @@ -0,0 +1,28 @@ +import { BlockCategory, createCompositeBlock, createCompositeBlockSelectField } from "@comet/blocks-admin"; +import { StandaloneMediaBlockData } from "@src/blocks.generated"; +import { MediaBlock } from "@src/common/blocks/MediaBlock"; +import { mediaAspectRatioOptions } from "@src/util/mediaAspectRatios"; +import { FormattedMessage } from "react-intl"; + +export const StandaloneMediaBlock = createCompositeBlock( + { + name: "Media", + displayName: , + blocks: { + media: { + block: MediaBlock, + }, + aspectRatio: { + block: createCompositeBlockSelectField({ + defaultValue: "16x9", + options: mediaAspectRatioOptions, + fieldProps: { label: , fullWidth: true }, + }), + }, + }, + }, + (block) => { + block.category = BlockCategory.Media; + return block; + }, +); diff --git a/demo/admin/src/common/blocks/TextLinkBlock.tsx b/demo/admin/src/common/blocks/TextLinkBlock.tsx index 2479b85be9..ba9f651306 100644 --- a/demo/admin/src/common/blocks/TextLinkBlock.tsx +++ b/demo/admin/src/common/blocks/TextLinkBlock.tsx @@ -2,4 +2,4 @@ import { createTextLinkBlock } from "@comet/cms-admin"; import { LinkBlock } from "./LinkBlock"; -export const TextLinkBlock = createTextLinkBlock({ link: LinkBlock, name: "DemoTextLink" }); +export const TextLinkBlock = createTextLinkBlock({ link: LinkBlock }); diff --git a/demo/admin/src/config.tsx b/demo/admin/src/config.tsx index 44d36bc363..6e9d14261e 100644 --- a/demo/admin/src/config.tsx +++ b/demo/admin/src/config.tsx @@ -1,4 +1,3 @@ -import { SiteConfig } from "@comet/cms-admin"; import { createContext, PropsWithChildren, useContext } from "react"; import cometConfig from "./comet-config.json"; @@ -23,11 +22,12 @@ export function createConfig() { apiUrl: environmentVariables.API_URL, adminUrl: environmentVariables.ADMIN_URL, sitesConfig: JSON.parse(environmentVariables.PUBLIC_SITE_CONFIGS) as PublicSiteConfig[], + buildDate: environmentVariables.BUILD_DATE, + buildNumber: environmentVariables.BUILD_NUMBER, + commitSha: environmentVariables.COMMIT_SHA, }; } -export type SitesConfig = Record; - export type Config = ReturnType; const ConfigContext = createContext(undefined); diff --git a/demo/admin/src/dashboard/Dashboard.tsx b/demo/admin/src/dashboard/DashboardPage.tsx similarity index 95% rename from demo/admin/src/dashboard/Dashboard.tsx rename to demo/admin/src/dashboard/DashboardPage.tsx index 381197d431..bafc8c5e55 100644 --- a/demo/admin/src/dashboard/Dashboard.tsx +++ b/demo/admin/src/dashboard/DashboardPage.tsx @@ -7,9 +7,10 @@ import backgroundImage1x from "./dashboard-image@1x.jpg"; import backgroundImage2x from "./dashboard-image@2x.jpg"; import { LatestContentUpdates } from "./LatestContentUpdates"; -const Dashboard = () => { +export function DashboardPage() { const intl = useIntl(); const isAllowed = useUserPermissionCheck(); + return ( { ); -}; - -export default Dashboard; +} diff --git a/demo/admin/src/links/EditLink.tsx b/demo/admin/src/documents/links/EditLink.tsx similarity index 100% rename from demo/admin/src/links/EditLink.tsx rename to demo/admin/src/documents/links/EditLink.tsx diff --git a/demo/admin/src/links/Link.tsx b/demo/admin/src/documents/links/Link.tsx similarity index 98% rename from demo/admin/src/links/Link.tsx rename to demo/admin/src/documents/links/Link.tsx index dc31b40570..85dfcfa927 100644 --- a/demo/admin/src/links/Link.tsx +++ b/demo/admin/src/documents/links/Link.tsx @@ -6,11 +6,12 @@ import { Chip } from "@mui/material"; import { LinkBlock } from "@src/common/blocks/LinkBlock"; import { GQLPageTreeNodeAdditionalFieldsFragment } from "@src/common/EditPageNode"; import { GQLLink, GQLLinkInput } from "@src/graphql.generated"; -import { EditLink } from "@src/links/EditLink"; import { categoryToUrlParam } from "@src/pageTree/pageTreeCategories"; import gql from "graphql-tag"; import { FormattedMessage } from "react-intl"; +import { EditLink } from "./EditLink"; + const rootBlocks = { content: LinkBlock, }; diff --git a/demo/admin/src/pages/EditPage.tsx b/demo/admin/src/documents/pages/EditPage.tsx similarity index 98% rename from demo/admin/src/pages/EditPage.tsx rename to demo/admin/src/documents/pages/EditPage.tsx index 982ab4de6b..9fb71e9c4b 100644 --- a/demo/admin/src/pages/EditPage.tsx +++ b/demo/admin/src/documents/pages/EditPage.tsx @@ -15,14 +15,14 @@ import { useSiteConfig, } from "@comet/cms-admin"; import { Button, IconButton } from "@mui/material"; -import { SeoBlock } from "@src/common/blocks/SeoBlock"; import { useContentScope } from "@src/common/ContentScopeProvider"; import { GQLPageTreeNodeCategory } from "@src/graphql.generated"; import { FormattedMessage, useIntl } from "react-intl"; import { useRouteMatch } from "react-router"; +import { PageContentBlock } from "./blocks/PageContentBlock"; +import { SeoBlock } from "./blocks/SeoBlock"; import { GQLEditPageQuery, GQLEditPageQueryVariables, GQLUpdatePageMutation, GQLUpdatePageMutationVariables } from "./EditPage.generated"; -import { PageContentBlock } from "./PageContentBlock"; const pageDependenciesQuery = gql` query PageDependencies($id: ID!, $offset: Int!, $limit: Int!, $forceRefresh: Boolean = false) { diff --git a/demo/admin/src/pages/Page.tsx b/demo/admin/src/documents/pages/Page.tsx similarity index 95% rename from demo/admin/src/pages/Page.tsx rename to demo/admin/src/documents/pages/Page.tsx index 88b781746f..b46641a834 100644 --- a/demo/admin/src/pages/Page.tsx +++ b/demo/admin/src/documents/pages/Page.tsx @@ -3,15 +3,15 @@ import { File, FileNotMenu } from "@comet/admin-icons"; import { createDocumentDependencyMethods, createDocumentRootBlocksMethods, DependencyInterface, DocumentInterface } from "@comet/cms-admin"; import { PageTreePage } from "@comet/cms-admin/lib/pages/pageTree/usePageTree"; import { Chip } from "@mui/material"; -import { SeoBlock } from "@src/common/blocks/SeoBlock"; import { GQLPageTreeNodeAdditionalFieldsFragment } from "@src/common/EditPageNode"; import { GQLPage, GQLPageInput } from "@src/graphql.generated"; import { categoryToUrlParam } from "@src/pageTree/pageTreeCategories"; import gql from "graphql-tag"; import { FormattedMessage } from "react-intl"; +import { PageContentBlock } from "./blocks/PageContentBlock"; +import { SeoBlock } from "./blocks/SeoBlock"; import { EditPage } from "./EditPage"; -import { PageContentBlock } from "./PageContentBlock"; const rootBlocks = { content: PageContentBlock, diff --git a/demo/admin/src/documents/pages/blocks/BasicStageBlock.tsx b/demo/admin/src/documents/pages/blocks/BasicStageBlock.tsx new file mode 100644 index 0000000000..3c1547fabd --- /dev/null +++ b/demo/admin/src/documents/pages/blocks/BasicStageBlock.tsx @@ -0,0 +1,65 @@ +import { createCompositeBlock, createCompositeBlockSelectField } from "@comet/blocks-admin"; +import { BasicStageBlockData } from "@src/blocks.generated"; +import { CallToActionListBlock } from "@src/common/blocks/CallToActionListBlock"; +import { HeadingBlock } from "@src/common/blocks/HeadingBlock"; +import { MediaBlock } from "@src/common/blocks/MediaBlock"; +import { RichTextBlock } from "@src/common/blocks/RichTextBlock"; +import { FormattedMessage } from "react-intl"; + +const overlayOptions: Array<{ value: BasicStageBlockData["overlay"]; label: string }> = [ + { value: 90, label: "90%" }, + { value: 80, label: "80%" }, + { value: 70, label: "70%" }, + { value: 60, label: "60%" }, + { value: 50, label: "50%" }, + { value: 40, label: "40%" }, + { value: 30, label: "30%" }, + { value: 20, label: "20%" }, + { value: 10, label: "10%" }, + { value: 0, label: "0%" }, +]; + +export const BasicStageBlock = createCompositeBlock({ + name: "BasicStage", + displayName: , + blocks: { + media: { + block: MediaBlock, + title: , + hiddenInSubroute: true, + }, + heading: { + block: HeadingBlock, + }, + text: { + block: RichTextBlock, + title: , + hiddenInSubroute: true, + }, + overlay: { + block: createCompositeBlockSelectField({ + defaultValue: 50, + options: overlayOptions, + fieldProps: { fullWidth: true }, + }), + title: , + hiddenInSubroute: true, + }, + alignment: { + block: createCompositeBlockSelectField({ + defaultValue: "left", + options: [ + { value: "left", label: }, + { value: "center", label: }, + ], + fieldProps: { fullWidth: true }, + }), + title: , + hiddenInSubroute: true, + }, + callToActionList: { + block: CallToActionListBlock, + title: , + }, + }, +}); diff --git a/demo/admin/src/documents/pages/blocks/BillboardTeaserBlock.tsx b/demo/admin/src/documents/pages/blocks/BillboardTeaserBlock.tsx new file mode 100644 index 0000000000..edba1f7e60 --- /dev/null +++ b/demo/admin/src/documents/pages/blocks/BillboardTeaserBlock.tsx @@ -0,0 +1,59 @@ +import { BlockCategory, createCompositeBlock, createCompositeBlockSelectField } from "@comet/blocks-admin"; +import { BillboardTeaserBlockData } from "@src/blocks.generated"; +import { CallToActionListBlock } from "@src/common/blocks/CallToActionListBlock"; +import { HeadingBlock } from "@src/common/blocks/HeadingBlock"; +import { MediaBlock } from "@src/common/blocks/MediaBlock"; +import { RichTextBlock } from "@src/common/blocks/RichTextBlock"; +import { FormattedMessage } from "react-intl"; + +const overlayOptions: Array<{ value: BillboardTeaserBlockData["overlay"]; label: string }> = [ + { value: 90, label: "90%" }, + { value: 80, label: "80%" }, + { value: 70, label: "70%" }, + { value: 60, label: "60%" }, + { value: 50, label: "50%" }, + { value: 40, label: "40%" }, + { value: 30, label: "30%" }, + { value: 20, label: "20%" }, + { value: 10, label: "10%" }, + { value: 0, label: "0%" }, +]; + +export const BillboardTeaserBlock = createCompositeBlock( + { + name: "BillboardTeaser", + displayName: , + blocks: { + media: { + block: MediaBlock, + title: , + hiddenInSubroute: true, + }, + heading: { + block: HeadingBlock, + }, + text: { + block: RichTextBlock, + title: , + hiddenInSubroute: true, + }, + overlay: { + block: createCompositeBlockSelectField({ + defaultValue: 50, + options: overlayOptions, + fieldProps: { fullWidth: true }, + }), + title: , + hiddenInSubroute: true, + }, + callToActionList: { + block: CallToActionListBlock, + title: , + }, + }, + }, + (block) => { + block.category = BlockCategory.Teaser; + return block; + }, +); diff --git a/demo/admin/src/documents/pages/blocks/ColumnsBlock.tsx b/demo/admin/src/documents/pages/blocks/ColumnsBlock.tsx new file mode 100644 index 0000000000..56d33db563 --- /dev/null +++ b/demo/admin/src/documents/pages/blocks/ColumnsBlock.tsx @@ -0,0 +1,133 @@ +import { + ColumnsLayoutPreview, + ColumnsLayoutPreviewContent, + ColumnsLayoutPreviewSpacing, + createBlocksBlock, + createColumnsBlock, +} from "@comet/blocks-admin"; +import { AnchorBlock } from "@comet/cms-admin"; +import { AccordionBlock } from "@src/common/blocks/AccordionBlock"; +import { MediaGalleryBlock } from "@src/common/blocks/MediaGalleryBlock"; +import { RichTextBlock } from "@src/common/blocks/RichTextBlock"; +import { SpaceBlock } from "@src/common/blocks/SpaceBlock"; +import { StandaloneCallToActionListBlock } from "@src/common/blocks/StandaloneCallToActionListBlock"; +import { StandaloneHeadingBlock } from "@src/common/blocks/StandaloneHeadingBlock"; +import { StandaloneMediaBlock } from "@src/common/blocks/StandaloneMediaBlock"; +import { FormattedMessage } from "react-intl"; + +const oneColumnLayouts = [ + { + name: "2-20-2", + columns: 1, + label: , + preview: ( + + + + + + ), + }, + { + name: "4-16-4", + columns: 1, + label: , + preview: ( + + + + + + ), + }, + { + name: "9-6-9", + columns: 1, + label: , + preview: ( + + + + + + ), + }, +]; + +const twoColumnLayouts = [ + { + name: "9-9", + columns: 2, + label: , + preview: ( + + + + + + + + ), + section: { + name: "sameWidth", + label: , + }, + }, + { + name: "12-6", + columns: 2, + label: , + preview: ( + + + + + + + + ), + section: { + name: "differentWidth", + label: , + }, + }, + { + name: "6-12", + columns: 2, + label: , + preview: ( + + + + + + + + ), + section: { + name: "differentWidth", + label: , + }, + }, +]; + +const ColumnsContentBlock = createBlocksBlock({ + name: "ColumnsContent", + supportedBlocks: { + accordion: AccordionBlock, + anchor: AnchorBlock, + richText: RichTextBlock, + space: SpaceBlock, + heading: StandaloneHeadingBlock, + callToActionList: StandaloneCallToActionListBlock, + media: StandaloneMediaBlock, + mediaGallery: MediaGalleryBlock, + }, +}); + +export const ColumnsBlock = createColumnsBlock({ + name: "Columns", + displayName: , + contentBlock: ColumnsContentBlock, + layouts: [...oneColumnLayouts, ...twoColumnLayouts], +}); diff --git a/demo/admin/src/documents/pages/blocks/ContentGroupBlock.tsx b/demo/admin/src/documents/pages/blocks/ContentGroupBlock.tsx new file mode 100644 index 0000000000..9dd7455147 --- /dev/null +++ b/demo/admin/src/documents/pages/blocks/ContentGroupBlock.tsx @@ -0,0 +1,64 @@ +import { BlockCategory, createBlocksBlock, createCompositeBlock, createCompositeBlockSelectField } from "@comet/blocks-admin"; +import { AnchorBlock } from "@comet/cms-admin"; +import { ContentGroupBlockData } from "@src/blocks.generated"; +import { AccordionBlock } from "@src/common/blocks/AccordionBlock"; +import { MediaGalleryBlock } from "@src/common/blocks/MediaGalleryBlock"; +import { RichTextBlock } from "@src/common/blocks/RichTextBlock"; +import { SpaceBlock } from "@src/common/blocks/SpaceBlock"; +import { StandaloneCallToActionListBlock } from "@src/common/blocks/StandaloneCallToActionListBlock"; +import { StandaloneHeadingBlock } from "@src/common/blocks/StandaloneHeadingBlock"; +import { StandaloneMediaBlock } from "@src/common/blocks/StandaloneMediaBlock"; +import { ReactNode } from "react"; +import { FormattedMessage } from "react-intl"; + +import { ColumnsBlock } from "./ColumnsBlock"; +import { KeyFactsBlock } from "./KeyFactsBlock"; +import { TeaserBlock } from "./TeaserBlock"; + +const backgroundColorOptions: Array<{ value: ContentGroupBlockData["backgroundColor"]; label: ReactNode }> = [ + { value: "default", label: }, + { value: "lightGray", label: }, + { value: "darkGray", label: }, +]; + +const ContentGroupContentBlock = createBlocksBlock({ + name: "ContentGroupContent", + supportedBlocks: { + accordion: AccordionBlock, + anchor: AnchorBlock, + space: SpaceBlock, + teaser: TeaserBlock, + richText: RichTextBlock, + heading: StandaloneHeadingBlock, + columns: ColumnsBlock, + callToActionList: StandaloneCallToActionListBlock, + keyFacts: KeyFactsBlock, + media: StandaloneMediaBlock, + mediaGallery: MediaGalleryBlock, + }, +}); + +export const ContentGroupBlock = createCompositeBlock( + { + name: "ContentGroup", + displayName: , + blocks: { + backgroundColor: { + block: createCompositeBlockSelectField({ + defaultValue: "default", + options: backgroundColorOptions, + fieldProps: { fullWidth: true, label: }, + }), + hiddenInSubroute: true, + }, + content: { + block: ContentGroupContentBlock, + title: , + }, + }, + }, + (block) => { + block.category = BlockCategory.Layout; + return block; + }, +); diff --git a/demo/admin/src/pages/blocks/FullWidthImageBlock.tsx b/demo/admin/src/documents/pages/blocks/FullWidthImageBlock.tsx similarity index 100% rename from demo/admin/src/pages/blocks/FullWidthImageBlock.tsx rename to demo/admin/src/documents/pages/blocks/FullWidthImageBlock.tsx diff --git a/demo/admin/src/pages/blocks/ImageLinkBlock.tsx b/demo/admin/src/documents/pages/blocks/ImageLinkBlock.tsx similarity index 100% rename from demo/admin/src/pages/blocks/ImageLinkBlock.tsx rename to demo/admin/src/documents/pages/blocks/ImageLinkBlock.tsx diff --git a/demo/admin/src/documents/pages/blocks/KeyFactsBlock.tsx b/demo/admin/src/documents/pages/blocks/KeyFactsBlock.tsx new file mode 100644 index 0000000000..54dc2644e8 --- /dev/null +++ b/demo/admin/src/documents/pages/blocks/KeyFactsBlock.tsx @@ -0,0 +1,12 @@ +import { createListBlock } from "@comet/blocks-admin"; +import { FormattedMessage } from "react-intl"; + +import { KeyFactsItemBlock } from "./KeyFactsItemBlock"; + +export const KeyFactsBlock = createListBlock({ + name: "KeyFacts", + displayName: , + block: KeyFactsItemBlock, + itemName: , + itemsName: , +}); diff --git a/demo/admin/src/documents/pages/blocks/KeyFactsItemBlock.tsx b/demo/admin/src/documents/pages/blocks/KeyFactsItemBlock.tsx new file mode 100644 index 0000000000..e48a196343 --- /dev/null +++ b/demo/admin/src/documents/pages/blocks/KeyFactsItemBlock.tsx @@ -0,0 +1,45 @@ +import { BlockCategory, createCompositeBlock, createCompositeBlockTextField } from "@comet/blocks-admin"; +import { createRichTextBlock, SvgImageBlock } from "@comet/cms-admin"; +import { LinkBlock } from "@src/common/blocks/LinkBlock"; +import { FormattedMessage } from "react-intl"; + +const DescriptionRichTextBlock = createRichTextBlock({ + link: LinkBlock, + rte: { + maxBlocks: 1, + supports: ["bold", "italic", "sub", "sup", "non-breaking-space", "soft-hyphen"], + }, + minHeight: 0, +}); + +export const KeyFactsItemBlock = createCompositeBlock( + { + name: "KeyFactsItem", + displayName: , + blocks: { + icon: { + block: SvgImageBlock, + title: , + }, + fact: { + block: createCompositeBlockTextField({ + fieldProps: { fullWidth: true, label: }, + }), + }, + label: { + block: createCompositeBlockTextField({ + fieldProps: { fullWidth: true, label: }, + }), + }, + description: { + block: DescriptionRichTextBlock, + title: , + }, + }, + }, + (block) => { + block.category = BlockCategory.TextAndContent; + block.previewContent = (state) => [{ type: "text", content: state.fact }]; + return block; + }, +); diff --git a/demo/admin/src/pages/blocks/MediaBlock.tsx b/demo/admin/src/documents/pages/blocks/MediaBlock.tsx similarity index 100% rename from demo/admin/src/pages/blocks/MediaBlock.tsx rename to demo/admin/src/documents/pages/blocks/MediaBlock.tsx diff --git a/demo/admin/src/pages/PageContentBlock.tsx b/demo/admin/src/documents/pages/blocks/PageContentBlock.tsx similarity index 58% rename from demo/admin/src/pages/PageContentBlock.tsx rename to demo/admin/src/documents/pages/blocks/PageContentBlock.tsx index dd11f7e36f..2bc69eb0af 100644 --- a/demo/admin/src/pages/PageContentBlock.tsx +++ b/demo/admin/src/documents/pages/blocks/PageContentBlock.tsx @@ -1,43 +1,51 @@ import { createBlocksBlock } from "@comet/blocks-admin"; import { AnchorBlock, DamImageBlock } from "@comet/cms-admin"; -import { HeadlineBlock } from "@src/common/blocks/HeadlineBlock"; +import { AccordionBlock } from "@src/common/blocks/AccordionBlock"; import { LinkListBlock } from "@src/common/blocks/LinkListBlock"; +import { MediaGalleryBlock } from "@src/common/blocks/MediaGalleryBlock"; import { RichTextBlock } from "@src/common/blocks/RichTextBlock"; import { SpaceBlock } from "@src/common/blocks/SpaceBlock"; +import { StandaloneCallToActionListBlock } from "@src/common/blocks/StandaloneCallToActionListBlock"; +import { StandaloneHeadingBlock } from "@src/common/blocks/StandaloneHeadingBlock"; +import { StandaloneMediaBlock } from "@src/common/blocks/StandaloneMediaBlock"; import { TextImageBlock } from "@src/common/blocks/TextImageBlock"; import { NewsDetailBlock } from "@src/news/blocks/NewsDetailBlock"; import { NewsListBlock } from "@src/news/blocks/NewsListBlock"; -import { LayoutBlock } from "@src/pages/blocks/LayoutBlock"; import { userGroupAdditionalItemFields } from "@src/userGroups/userGroupAdditionalItemFields"; import { UserGroupChip } from "@src/userGroups/UserGroupChip"; import { UserGroupContextMenuItem } from "@src/userGroups/UserGroupContextMenuItem"; -import { ColumnsBlock } from "./blocks/ColumnsBlock"; -import { FullWidthImageBlock } from "./blocks/FullWidthImageBlock"; -import { ImageLinkBlock } from "./blocks/ImageLinkBlock"; -import { MediaBlock } from "./blocks/MediaBlock"; -import { TeaserBlock } from "./blocks/TeaserBlock"; -import { TwoListsBlock } from "./blocks/TwoListsBlock"; +import { BillboardTeaserBlock } from "./BillboardTeaserBlock"; +import { ColumnsBlock } from "./ColumnsBlock"; +import { ContentGroupBlock } from "./ContentGroupBlock"; +import { FullWidthImageBlock } from "./FullWidthImageBlock"; +import { ImageLinkBlock } from "./ImageLinkBlock"; +import { KeyFactsBlock } from "./KeyFactsBlock"; +import { TeaserBlock } from "./TeaserBlock"; export const PageContentBlock = createBlocksBlock({ name: "PageContent", supportedBlocks: { - space: SpaceBlock, - richtext: RichTextBlock, - headline: HeadlineBlock, + accordion: AccordionBlock, + anchor: AnchorBlock, + billboardTeaser: BillboardTeaserBlock, + callToActionList: StandaloneCallToActionListBlock, + columns: ColumnsBlock, + contentGroup: ContentGroupBlock, + fullWidthImage: FullWidthImageBlock, + heading: StandaloneHeadingBlock, image: DamImageBlock, - textImage: TextImageBlock, + imageLink: ImageLinkBlock, + keyFacts: KeyFactsBlock, linkList: LinkListBlock, - fullWidthImage: FullWidthImageBlock, - columns: ColumnsBlock, - anchor: AnchorBlock, - twoLists: TwoListsBlock, - media: MediaBlock, - teaser: TeaserBlock, + media: StandaloneMediaBlock, + mediaGallery: MediaGalleryBlock, newsDetail: NewsDetailBlock, - imageLink: ImageLinkBlock, newsList: NewsListBlock, - layout: LayoutBlock, + richText: RichTextBlock, + space: SpaceBlock, + teaser: TeaserBlock, + textImage: TextImageBlock, }, additionalItemFields: { ...userGroupAdditionalItemFields, diff --git a/demo/admin/src/documents/pages/blocks/SeoBlock.tsx b/demo/admin/src/documents/pages/blocks/SeoBlock.tsx new file mode 100644 index 0000000000..c29b51a824 --- /dev/null +++ b/demo/admin/src/documents/pages/blocks/SeoBlock.tsx @@ -0,0 +1,3 @@ +import { createSeoBlock, PixelImageBlock } from "@comet/cms-admin"; + +export const SeoBlock = createSeoBlock({ image: PixelImageBlock }); diff --git a/demo/admin/src/documents/pages/blocks/StageBlock.tsx b/demo/admin/src/documents/pages/blocks/StageBlock.tsx new file mode 100644 index 0000000000..1ac0f8c983 --- /dev/null +++ b/demo/admin/src/documents/pages/blocks/StageBlock.tsx @@ -0,0 +1,14 @@ +import { createListBlock } from "@comet/blocks-admin"; +import { FormattedMessage } from "react-intl"; + +import { BasicStageBlock } from "./BasicStageBlock"; + +/* If you need multiple stage blocks, you should use createBlocksBlock instead of createListBlock */ +export const StageBlock = createListBlock({ + name: "Stage", + displayName: , + block: BasicStageBlock, + maxVisibleBlocks: 1, + itemName: , + itemsName: , +}); diff --git a/demo/admin/src/documents/pages/blocks/TeaserBlock.tsx b/demo/admin/src/documents/pages/blocks/TeaserBlock.tsx new file mode 100644 index 0000000000..b9c238f15e --- /dev/null +++ b/demo/admin/src/documents/pages/blocks/TeaserBlock.tsx @@ -0,0 +1,12 @@ +import { createListBlock } from "@comet/blocks-admin"; +import { FormattedMessage } from "react-intl"; + +import { TeaserItemBlock } from "./TeaserItemBlock"; + +export const TeaserBlock = createListBlock({ + name: "Teaser", + displayName: , + block: TeaserItemBlock, + itemName: , + itemsName: , +}); diff --git a/demo/admin/src/documents/pages/blocks/TeaserItemBlock.tsx b/demo/admin/src/documents/pages/blocks/TeaserItemBlock.tsx new file mode 100644 index 0000000000..69af6e921c --- /dev/null +++ b/demo/admin/src/documents/pages/blocks/TeaserItemBlock.tsx @@ -0,0 +1,45 @@ +import { BlockCategory, createCompositeBlock, createCompositeBlockTextField } from "@comet/blocks-admin"; +import { createRichTextBlock } from "@comet/cms-admin"; +import { LinkBlock } from "@src/common/blocks/LinkBlock"; +import { MediaBlock } from "@src/common/blocks/MediaBlock"; +import { TextLinkBlock } from "@src/common/blocks/TextLinkBlock"; +import { FormattedMessage } from "react-intl"; + +const DescriptionRichTextBlock = createRichTextBlock({ + link: LinkBlock, + rte: { + maxBlocks: 1, + supports: ["bold", "italic", "sub", "sup", "non-breaking-space", "soft-hyphen"], + }, + minHeight: 0, +}); + +export const TeaserItemBlock = createCompositeBlock( + { + name: "TeaserItem", + displayName: , + blocks: { + media: { + block: MediaBlock, + title: , + }, + title: { + block: createCompositeBlockTextField({ + fieldProps: { fullWidth: true, label: }, + }), + }, + description: { + block: DescriptionRichTextBlock, + title: , + }, + link: { + block: TextLinkBlock, + }, + }, + }, + (block) => { + block.category = BlockCategory.Teaser; + block.previewContent = (state) => [{ type: "text", content: state.title }]; + return block; + }, +); diff --git a/demo/admin/src/environment.ts b/demo/admin/src/environment.ts index dace480059..a806419676 100644 --- a/demo/admin/src/environment.ts +++ b/demo/admin/src/environment.ts @@ -1 +1 @@ -export const environment = ["API_URL", "ADMIN_URL", "PUBLIC_SITE_CONFIGS", "COMET_DEMO_API_URL", "BUILD_DATE", "BUILD_NUMBER", "COMMIT_SHA"] as const; +export const environment = ["API_URL", "ADMIN_URL", "PREVIEW_URL", "PUBLIC_SITE_CONFIGS", "BUILD_DATE", "BUILD_NUMBER", "COMMIT_SHA"]; diff --git a/demo/admin/src/footer/EditFooterPage.tsx b/demo/admin/src/footer/EditFooterPage.tsx new file mode 100644 index 0000000000..44812f163e --- /dev/null +++ b/demo/admin/src/footer/EditFooterPage.tsx @@ -0,0 +1,191 @@ +import { gql, useMutation, useQuery } from "@apollo/client"; +import { MainContent, messages, SaveButton, Stack, StackToolbar, ToolbarActions, ToolbarFillSpace, ToolbarTitleItem } from "@comet/admin"; +import { Save } from "@comet/admin-icons"; +import { AdminComponentRoot, BlockState } from "@comet/blocks-admin"; +import { + BlockPreviewWithTabs, + ContentScopeIndicator, + resolveHasSaveConflict, + useBlockPreview, + useCmsBlockContext, + useContentScopeConfig, + useSaveConflictQuery, + useSiteConfig, +} from "@comet/cms-admin"; +import { FooterContentBlockInput } from "@src/blocks.generated"; +import { useContentScope } from "@src/common/ContentScopeProvider"; +import isEqual from "lodash.isequal"; +import { useEffect, useState } from "react"; +import { FormattedMessage } from "react-intl"; +import { useRouteMatch } from "react-router"; + +import { FooterContentBlock } from "./blocks/FooterContentBlock"; +import { + GQLCheckForChangesFooterQuery, + GQLCheckForChangesFooterQueryVariables, + GQLFooterQuery, + GQLFooterQueryVariables, + GQLSaveFooterMutation, + GQLSaveFooterMutationVariables, + namedOperations, +} from "./EditFooterPage.generated"; + +export function EditFooterPage(): JSX.Element | null { + const { scope } = useContentScope(); + const siteConfig = useSiteConfig({ scope }); + const [footerState, setFooterState] = useState>(FooterContentBlock.defaultValues()); + const [hasChanges, setHasChanges] = useState(false); + const [referenceContent, setReferenceContent] = useState(null); + const match = useRouteMatch(); + const previewApi = useBlockPreview(); + const blockContext = useCmsBlockContext(); + + useContentScopeConfig({ redirectPathAfterChange: "/project-snips/footer" }); + + const { data, refetch, loading } = useQuery(footerQuery, { + variables: { + scope, + }, + }); + + const saveConflict = useSaveConflictQuery( + checkForChangesQuery, + { + variables: { + scope, + }, + resolveHasConflict: (checkForChangesData) => { + return resolveHasSaveConflict(data?.footer?.updatedAt, checkForChangesData?.footer?.updatedAt); + }, + }, + { + hasChanges, + loadLatestVersion: async () => { + await refetch(); + }, + onDiscardButtonPressed: async () => { + await refetch(); + }, + }, + ); + + const [update, { loading: saving, error: hasSaveErrors }] = useMutation( + saveFooterMutation, + { refetchQueries: !data?.footer ? [namedOperations.Query.Footer] : [] }, + ); + + useEffect(() => { + if (data) { + if (data.footer) { + const content = FooterContentBlock.input2State(data.footer.content); + setFooterState(content); + setReferenceContent(FooterContentBlock.state2Output(content)); + } else { + const state = FooterContentBlock.defaultValues(); + setFooterState(state); + setReferenceContent(FooterContentBlock.state2Output(state)); + } + } + }, [data]); + + useEffect(() => { + const equal = isEqual(referenceContent, footerState ? FooterContentBlock.state2Output(footerState) : null); + setHasChanges(!equal); + }, [footerState, referenceContent]); + + if (loading) { + return null; + } + + const handleSavePage = async () => { + const hasSaveConflict = await saveConflict.checkForConflicts(); + if (hasSaveConflict) { + return; // dialogs open for the user to handle the conflict + } + + const input = { content: FooterContentBlock.state2Output(footerState) }; + return update({ + variables: { input, scope }, + }); + }; + + const tabs = [ + { + key: "content", + label: , + content: ( + + + + ), + }, + ]; + + const previewState = FooterContentBlock.createPreviewState(footerState, { + ...blockContext, + parentUrl: match.url, + showVisibleOnly: previewApi.showOnlyVisible, + }); + + return ( + + }> + + + + + + } + > + + + + + + + {tabs} + + + {saveConflict.dialogs} + + ); +} + +const footerQuery = gql` + query Footer($scope: FooterScopeInput!) { + footer(scope: $scope) { + id + content + scope { + domain + language + } + updatedAt + } + } +`; + +const saveFooterMutation = gql` + mutation SaveFooter($input: FooterInput!, $scope: FooterScopeInput!) { + saveFooter(input: $input, scope: $scope) { + id + content + updatedAt + } + } +`; + +const checkForChangesQuery = gql` + query CheckForChangesFooter($scope: FooterScopeInput!) { + footer(scope: $scope) { + updatedAt + } + } +`; diff --git a/demo/admin/src/footer/blocks/FooterContentBlock.tsx b/demo/admin/src/footer/blocks/FooterContentBlock.tsx new file mode 100644 index 0000000000..571f86e530 --- /dev/null +++ b/demo/admin/src/footer/blocks/FooterContentBlock.tsx @@ -0,0 +1,35 @@ +import { createCompositeBlock, createCompositeBlockTextField } from "@comet/blocks-admin"; +import { DamImageBlock } from "@comet/cms-admin"; +import { LinkListBlock } from "@src/common/blocks/LinkListBlock"; +import { RichTextBlock } from "@src/common/blocks/RichTextBlock"; +import { FormattedMessage } from "react-intl"; + +export const FooterContentBlock = createCompositeBlock({ + name: "FooterContent", + displayName: null, + blocks: { + text: { + block: RichTextBlock, + title: , + hiddenInSubroute: true, + }, + image: { + block: DamImageBlock, + title: , + hiddenInSubroute: true, + }, + linkList: { + block: LinkListBlock, + title: , + }, + copyrightNotice: { + block: createCompositeBlockTextField({ + fieldProps: { + label: , + fullWidth: true, + }, + }), + hiddenInSubroute: true, + }, + }, +}); diff --git a/demo/admin/src/loader.ts b/demo/admin/src/loader.ts index 603557ab55..51eeede9e4 100644 --- a/demo/admin/src/loader.ts +++ b/demo/admin/src/loader.ts @@ -1,13 +1,16 @@ -import App from "./App"; +import { createElement } from "react"; +import { render } from "react-dom"; + +import { App } from "./App"; const loadHtml = () => { const rootElement = document.querySelector("#root"); if (!rootElement) return false; - App.render(rootElement); + render(createElement(App), rootElement); }; -if (["interactive", "complete"].indexOf(document.readyState) !== -1) { +if (["interactive", "complete"].includes(document.readyState)) { loadHtml(); } else { document.addEventListener("DOMContentLoaded", loadHtml, false); diff --git a/demo/admin/src/pages/mainMenu/MainMenu.tsx b/demo/admin/src/mainMenu/MainMenu.tsx similarity index 100% rename from demo/admin/src/pages/mainMenu/MainMenu.tsx rename to demo/admin/src/mainMenu/MainMenu.tsx diff --git a/demo/admin/src/pages/mainMenu/components/EditMainMenuItem.tsx b/demo/admin/src/mainMenu/components/EditMainMenuItem.tsx similarity index 100% rename from demo/admin/src/pages/mainMenu/components/EditMainMenuItem.tsx rename to demo/admin/src/mainMenu/components/EditMainMenuItem.tsx diff --git a/demo/admin/src/pages/mainMenu/components/MainMenuItems.tsx b/demo/admin/src/mainMenu/components/MainMenuItems.tsx similarity index 100% rename from demo/admin/src/pages/mainMenu/components/MainMenuItems.tsx rename to demo/admin/src/mainMenu/components/MainMenuItems.tsx diff --git a/demo/admin/src/news/blocks/NewsContentBlock.tsx b/demo/admin/src/news/blocks/NewsContentBlock.tsx index 9c8867116d..efef98f4e4 100644 --- a/demo/admin/src/news/blocks/NewsContentBlock.tsx +++ b/demo/admin/src/news/blocks/NewsContentBlock.tsx @@ -1,14 +1,14 @@ import { createBlocksBlock } from "@comet/blocks-admin"; import { DamImageBlock } from "@comet/cms-admin"; -import { HeadlineBlock } from "@src/common/blocks/HeadlineBlock"; +import { HeadingBlock } from "@src/common/blocks/HeadingBlock"; import { RichTextBlock } from "@src/common/blocks/RichTextBlock"; import { TextImageBlock } from "@src/common/blocks/TextImageBlock"; export const NewsContentBlock = createBlocksBlock({ name: "NewsContentBlock", supportedBlocks: { - headline: HeadlineBlock, - richtext: RichTextBlock, + heading: HeadingBlock, + richText: RichTextBlock, image: DamImageBlock, textImage: TextImageBlock, }, diff --git a/demo/admin/src/pages/blocks/ColumnsBlock.tsx b/demo/admin/src/pages/blocks/ColumnsBlock.tsx deleted file mode 100644 index e075eb829f..0000000000 --- a/demo/admin/src/pages/blocks/ColumnsBlock.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { - ColumnsLayoutPreview, - ColumnsLayoutPreviewContent, - ColumnsLayoutPreviewSpacing, - createBlocksBlock, - createColumnsBlock, - SpaceBlock, -} from "@comet/blocks-admin"; -import { DamImageBlock } from "@comet/cms-admin"; -import { HeadlineBlock } from "@src/common/blocks/HeadlineBlock"; -import { LinkListBlock } from "@src/common/blocks/LinkListBlock"; -import { RichTextBlock } from "@src/common/blocks/RichTextBlock"; -import { FormattedMessage } from "react-intl"; - -const ColumnsContentBlock = createBlocksBlock({ - name: "ColumnsContent", - supportedBlocks: { - space: SpaceBlock, - richtext: RichTextBlock, - headline: HeadlineBlock, - image: DamImageBlock, - linkList: LinkListBlock, - }, -}); - -const ColumnsBlock = createColumnsBlock({ - name: "Columns", - displayName: "Columns", - layouts: [ - { - name: "one-column", - label: "One column", - columns: 1, - preview: ( - - - - - - ), - }, - { - name: "two-columns", - label: "Two columns", - columns: 2, - preview: ( - - - - - - ), - section: { - name: "same-width", - label: , - }, - }, - { - name: "two-columns-12-6", - label: "Two columns 12-6", - columns: 2, - preview: ( - - - - - - - - ), - section: { - name: "different-width", - label: , - }, - }, - ], - contentBlock: ColumnsContentBlock, -}); - -export { ColumnsBlock }; diff --git a/demo/admin/src/pages/blocks/TeaserBlock.tsx b/demo/admin/src/pages/blocks/TeaserBlock.tsx deleted file mode 100644 index fa2f2bd838..0000000000 --- a/demo/admin/src/pages/blocks/TeaserBlock.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { BlockCategory, createCompositeBlock } from "@comet/blocks-admin"; -import { DamImageBlock } from "@comet/cms-admin"; -import { HeadlineBlock } from "@src/common/blocks/HeadlineBlock"; -import { LinkListBlock } from "@src/common/blocks/LinkListBlock"; -import { ColumnsBlock } from "@src/pages/blocks/ColumnsBlock"; -import { FormattedMessage } from "react-intl"; - -const TeaserBlock = createCompositeBlock( - { - name: "Teaser", - displayName: , - blocks: { - // Normal - headline: { - block: HeadlineBlock, - title: , - }, - // Nested - image: { - block: DamImageBlock, - title: , - nested: true, - }, - // Subroutes - links: { - block: LinkListBlock, - title: , - }, - // Nested inner subroutes - buttons: { - block: LinkListBlock, - title: , - nested: true, - }, - columns: { - block: ColumnsBlock, - title: , - }, - }, - }, - (block) => { - block.category = BlockCategory.Teaser; - return block; - }, -); - -export { TeaserBlock }; diff --git a/demo/admin/src/pages/blocks/TwoListsBlock.tsx b/demo/admin/src/pages/blocks/TwoListsBlock.tsx deleted file mode 100644 index 1d42d9b793..0000000000 --- a/demo/admin/src/pages/blocks/TwoListsBlock.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { createCompositeBlock, createListBlock } from "@comet/blocks-admin"; -import { customBlockCategory } from "@src/common/blocks/customBlockCategories"; -import { HeadlineBlock } from "@src/common/blocks/HeadlineBlock"; - -const TwoListsListBlock = createListBlock({ - name: "TwoListsList", - block: HeadlineBlock, -}); - -export const TwoListsBlock = createCompositeBlock( - { - name: "TwoLists", - displayName: "Two Lists", - blocks: { - list1: { - block: TwoListsListBlock, - title: "List 1", - }, - list2: { - block: TwoListsListBlock, - title: "List 2", - }, - }, - }, - (block) => { - block.category = customBlockCategory; - return block; - }, -); diff --git a/demo/admin/src/util/mediaAspectRatios.ts b/demo/admin/src/util/mediaAspectRatios.ts new file mode 100644 index 0000000000..6079eb8d09 --- /dev/null +++ b/demo/admin/src/util/mediaAspectRatios.ts @@ -0,0 +1,19 @@ +import { StandaloneMediaBlockData } from "@src/blocks.generated"; +import { ReactNode } from "react"; + +export const mediaAspectRatioOptions: Array<{ + value: StandaloneMediaBlockData["aspectRatio"]; + label: ReactNode; +}> = [ + { label: "16:9", value: "16x9" }, + { label: "4:3", value: "4x3" }, + { label: "3:2", value: "3x2" }, + { label: "3:1", value: "3x1" }, + { label: "2:1", value: "2x1" }, + { label: "1:1", value: "1x1" }, + { label: "1:2", value: "1x2" }, + { label: "1:3", value: "1x3" }, + { label: "2:3", value: "2x3" }, + { label: "3:4", value: "3x4" }, + { label: "9:16", value: "9x16" }, +]; diff --git a/demo/api/block-meta.json b/demo/api/block-meta.json index 2537a8a4df..7e01393068 100644 --- a/demo/api/block-meta.json +++ b/demo/api/block-meta.json @@ -1,4 +1,177 @@ [ + { + "name": "Accordion", + "fields": [ + { + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "AccordionItem", + "nullable": false + } + ] + }, + "nullable": false + } + ], + "inputFields": [ + { + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "AccordionItem", + "nullable": false + } + ] + }, + "nullable": false + } + ] + }, + { + "name": "AccordionContent", + "fields": [ + { + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "type", + "kind": "String", + "nullable": false + }, + { + "name": "props", + "kind": "OneOfBlocks", + "blocks": { + "richText": "RichText", + "heading": "StandaloneHeading", + "space": "Space", + "callToActionList": "StandaloneCallToActionList" + }, + "nullable": false + } + ] + }, + "nullable": false + } + ], + "inputFields": [ + { + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "type", + "kind": "String", + "nullable": false + }, + { + "name": "props", + "kind": "OneOfBlocks", + "blocks": { + "richText": "RichText", + "heading": "StandaloneHeading", + "space": "Space", + "callToActionList": "StandaloneCallToActionList" + }, + "nullable": false + } + ] + }, + "nullable": false + } + ] + }, + { + "name": "AccordionItem", + "fields": [ + { + "name": "title", + "kind": "String", + "nullable": true + }, + { + "name": "content", + "kind": "Block", + "block": "AccordionContent", + "nullable": false + }, + { + "name": "openByDefault", + "kind": "Boolean", + "nullable": false + } + ], + "inputFields": [ + { + "name": "title", + "kind": "String", + "nullable": true + }, + { + "name": "content", + "kind": "Block", + "block": "AccordionContent", + "nullable": false + }, + { + "name": "openByDefault", + "kind": "Boolean", + "nullable": false + } + ] + }, { "name": "Anchor", "fields": [ @@ -17,15 +190,321 @@ ] }, { - "name": "Columns", + "name": "BasicStage", + "fields": [ + { + "name": "media", + "kind": "Block", + "block": "Media", + "nullable": false + }, + { + "name": "heading", + "kind": "Block", + "block": "Heading", + "nullable": false + }, + { + "name": "text", + "kind": "Block", + "block": "RichText", + "nullable": false + }, + { + "name": "overlay", + "kind": "Number", + "nullable": false + }, + { + "name": "alignment", + "kind": "Enum", + "enum": [ + "left", + "center" + ], + "nullable": false + }, + { + "name": "callToActionList", + "kind": "Block", + "block": "CallToActionList", + "nullable": false + } + ], + "inputFields": [ + { + "name": "media", + "kind": "Block", + "block": "Media", + "nullable": false + }, + { + "name": "heading", + "kind": "Block", + "block": "Heading", + "nullable": false + }, + { + "name": "text", + "kind": "Block", + "block": "RichText", + "nullable": false + }, + { + "name": "overlay", + "kind": "Number", + "nullable": false + }, + { + "name": "alignment", + "kind": "Enum", + "enum": [ + "left", + "center" + ], + "nullable": false + }, + { + "name": "callToActionList", + "kind": "Block", + "block": "CallToActionList", + "nullable": false + } + ] + }, + { + "name": "BillboardTeaser", + "fields": [ + { + "name": "media", + "kind": "Block", + "block": "Media", + "nullable": false + }, + { + "name": "heading", + "kind": "Block", + "block": "Heading", + "nullable": false + }, + { + "name": "text", + "kind": "Block", + "block": "RichText", + "nullable": false + }, + { + "name": "overlay", + "kind": "Number", + "nullable": false + }, + { + "name": "callToActionList", + "kind": "Block", + "block": "CallToActionList", + "nullable": false + } + ], + "inputFields": [ + { + "name": "media", + "kind": "Block", + "block": "Media", + "nullable": false + }, + { + "name": "heading", + "kind": "Block", + "block": "Heading", + "nullable": false + }, + { + "name": "text", + "kind": "Block", + "block": "RichText", + "nullable": false + }, + { + "name": "overlay", + "kind": "Number", + "nullable": false + }, + { + "name": "callToActionList", + "kind": "Block", + "block": "CallToActionList", + "nullable": false + } + ] + }, + { + "name": "CallToAction", + "fields": [ + { + "name": "textLink", + "kind": "Block", + "block": "TextLink", + "nullable": false + }, + { + "name": "variant", + "kind": "Enum", + "enum": [ + "Contained", + "Outlined", + "Text" + ], + "nullable": false + } + ], + "inputFields": [ + { + "name": "textLink", + "kind": "Block", + "block": "TextLink", + "nullable": false + }, + { + "name": "variant", + "kind": "Enum", + "enum": [ + "Contained", + "Outlined", + "Text" + ], + "nullable": false + } + ] + }, + { + "name": "CallToActionList", + "fields": [ + { + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "CallToAction", + "nullable": false + } + ] + }, + "nullable": false + } + ], + "inputFields": [ + { + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "CallToAction", + "nullable": false + } + ] + }, + "nullable": false + } + ] + }, + { + "name": "Columns", + "fields": [ + { + "name": "layout", + "kind": "String", + "nullable": false + }, + { + "name": "columns", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "ColumnsContent", + "nullable": false + } + ] + }, + "nullable": false + } + ], + "inputFields": [ + { + "name": "layout", + "kind": "String", + "nullable": false + }, + { + "name": "columns", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "ColumnsContent", + "nullable": false + } + ] + }, + "nullable": false + } + ] + }, + { + "name": "ColumnsContent", "fields": [ { - "name": "layout", - "kind": "String", - "nullable": false - }, - { - "name": "columns", + "name": "blocks", "kind": "NestedObjectList", "object": { "fields": [ @@ -39,10 +518,24 @@ "kind": "Boolean", "nullable": false }, + { + "name": "type", + "kind": "String", + "nullable": false + }, { "name": "props", - "kind": "Block", - "block": "ColumnsContent", + "kind": "OneOfBlocks", + "blocks": { + "accordion": "Accordion", + "anchor": "Anchor", + "richtext": "RichText", + "space": "Space", + "heading": "StandaloneHeading", + "callToActionList": "StandaloneCallToActionList", + "media": "StandaloneMedia", + "mediaGallery": "MediaGallery" + }, "nullable": false } ] @@ -52,12 +545,7 @@ ], "inputFields": [ { - "name": "layout", - "kind": "String", - "nullable": false - }, - { - "name": "columns", + "name": "blocks", "kind": "NestedObjectList", "object": { "fields": [ @@ -71,10 +559,24 @@ "kind": "Boolean", "nullable": false }, + { + "name": "type", + "kind": "String", + "nullable": false + }, { "name": "props", - "kind": "Block", - "block": "ColumnsContent", + "kind": "OneOfBlocks", + "blocks": { + "accordion": "Accordion", + "anchor": "Anchor", + "richtext": "RichText", + "space": "Space", + "heading": "StandaloneHeading", + "callToActionList": "StandaloneCallToActionList", + "media": "StandaloneMedia", + "mediaGallery": "MediaGallery" + }, "nullable": false } ] @@ -84,7 +586,46 @@ ] }, { - "name": "ColumnsContent", + "name": "ContentGroup", + "fields": [ + { + "name": "content", + "kind": "Block", + "block": "ContentGroupContent", + "nullable": false + }, + { + "name": "backgroundColor", + "kind": "Enum", + "enum": [ + "default", + "lightGray", + "darkGray" + ], + "nullable": false + } + ], + "inputFields": [ + { + "name": "content", + "kind": "Block", + "block": "ContentGroupContent", + "nullable": false + }, + { + "name": "backgroundColor", + "kind": "Enum", + "enum": [ + "default", + "lightGray", + "darkGray" + ], + "nullable": false + } + ] + }, + { + "name": "ContentGroupContent", "fields": [ { "name": "blocks", @@ -110,11 +651,17 @@ "name": "props", "kind": "OneOfBlocks", "blocks": { + "accordion": "Accordion", + "anchor": "Anchor", "space": "Space", - "richtext": "RichText", - "headline": "Headline", - "image": "DamImage", - "linkList": "LinkList" + "teaser": "Teaser", + "richText": "RichText", + "heading": "StandaloneHeading", + "columns": "Columns", + "callToActionList": "StandaloneCallToActionList", + "keyFacts": "KeyFacts", + "media": "StandaloneMedia", + "mediaGallery": "MediaGallery" }, "nullable": false } @@ -148,11 +695,17 @@ "name": "props", "kind": "OneOfBlocks", "blocks": { + "accordion": "Accordion", + "anchor": "Anchor", "space": "Space", - "richtext": "RichText", - "headline": "Headline", - "image": "DamImage", - "linkList": "LinkList" + "teaser": "Teaser", + "richText": "RichText", + "heading": "StandaloneHeading", + "columns": "Columns", + "callToActionList": "StandaloneCallToActionList", + "keyFacts": "KeyFacts", + "media": "StandaloneMedia", + "mediaGallery": "MediaGallery" }, "nullable": false } @@ -402,76 +955,6 @@ } ] }, - { - "name": "DemoSpace", - "fields": [ - { - "name": "spacing", - "kind": "Enum", - "enum": [ - "d150", - "d200", - "d250", - "d300", - "d350", - "d400", - "d450", - "d500", - "d550", - "d600" - ], - "nullable": false - } - ], - "inputFields": [ - { - "name": "spacing", - "kind": "Enum", - "enum": [ - "d150", - "d200", - "d250", - "d300", - "d350", - "d400", - "d450", - "d500", - "d550", - "d600" - ], - "nullable": false - } - ] - }, - { - "name": "DemoTextLink", - "fields": [ - { - "name": "text", - "kind": "String", - "nullable": false - }, - { - "name": "link", - "kind": "Block", - "block": "Link", - "nullable": false - } - ], - "inputFields": [ - { - "name": "text", - "kind": "String", - "nullable": false - }, - { - "name": "link", - "kind": "Block", - "block": "Link", - "nullable": false - } - ] - }, { "name": "EmailLink", "fields": [ @@ -520,19 +1003,19 @@ "name": "FooterContent", "fields": [ { - "name": "popularTopicsLinks", + "name": "text", "kind": "Block", - "block": "LinkList", + "block": "RichText", "nullable": false }, { - "name": "aboutLinks", + "name": "image", "kind": "Block", - "block": "LinkList", + "block": "DamImage", "nullable": false }, { - "name": "bottomLinks", + "name": "linkList", "kind": "Block", "block": "LinkList", "nullable": false @@ -541,33 +1024,23 @@ "name": "copyrightNotice", "kind": "String", "nullable": false - }, - { - "name": "location", - "kind": "String", - "nullable": false - }, - { - "name": "contactUs", - "kind": "String", - "nullable": false } ], "inputFields": [ { - "name": "popularTopicsLinks", + "name": "text", "kind": "Block", - "block": "LinkList", + "block": "RichText", "nullable": false }, { - "name": "aboutLinks", + "name": "image", "kind": "Block", - "block": "LinkList", + "block": "DamImage", "nullable": false }, { - "name": "bottomLinks", + "name": "linkList", "kind": "Block", "block": "LinkList", "nullable": false @@ -576,102 +1049,6 @@ "name": "copyrightNotice", "kind": "String", "nullable": false - }, - { - "name": "location", - "kind": "String", - "nullable": false - }, - { - "name": "contactUs", - "kind": "String", - "nullable": false - } - ] - }, - { - "name": "FooterLinkSection", - "fields": [ - { - "name": "title", - "kind": "String", - "nullable": false - }, - { - "name": "links", - "kind": "Block", - "block": "LinkList", - "nullable": false - } - ], - "inputFields": [ - { - "name": "title", - "kind": "String", - "nullable": false - }, - { - "name": "links", - "kind": "Block", - "block": "LinkList", - "nullable": false - } - ] - }, - { - "name": "FooterTopLinks", - "fields": [ - { - "name": "blocks", - "kind": "NestedObjectList", - "object": { - "fields": [ - { - "name": "key", - "kind": "String", - "nullable": false - }, - { - "name": "visible", - "kind": "Boolean", - "nullable": false - }, - { - "name": "props", - "kind": "Block", - "block": "FooterLinkSection", - "nullable": false - } - ] - }, - "nullable": false - } - ], - "inputFields": [ - { - "name": "blocks", - "kind": "NestedObjectList", - "object": { - "fields": [ - { - "name": "key", - "kind": "String", - "nullable": false - }, - { - "name": "visible", - "kind": "Boolean", - "nullable": false - }, - { - "name": "props", - "kind": "Block", - "block": "FooterLinkSection", - "nullable": false - } - ] - }, - "nullable": false } ] }, @@ -707,12 +1084,13 @@ ] }, { - "name": "Headline", + "name": "Heading", "fields": [ { "name": "eyebrow", - "kind": "String", - "nullable": true + "kind": "Block", + "block": "RichText", + "nullable": false }, { "name": "headline", @@ -721,15 +1099,15 @@ "nullable": false }, { - "name": "level", + "name": "htmlTag", "kind": "Enum", "enum": [ - "header-one", - "header-two", - "header-three", - "header-four", - "header-five", - "header-six" + "H1", + "H2", + "H3", + "H4", + "H5", + "H6" ], "nullable": false } @@ -737,8 +1115,9 @@ "inputFields": [ { "name": "eyebrow", - "kind": "String", - "nullable": true + "kind": "Block", + "block": "RichText", + "nullable": false }, { "name": "headline", @@ -747,15 +1126,15 @@ "nullable": false }, { - "name": "level", + "name": "htmlTag", "kind": "Enum", "enum": [ - "header-one", - "header-two", - "header-three", - "header-four", - "header-five", - "header-six" + "H1", + "H2", + "H3", + "H4", + "H5", + "H6" ], "nullable": false } @@ -848,6 +1227,114 @@ } ] }, + { + "name": "KeyFacts", + "fields": [ + { + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "KeyFactsItem", + "nullable": false + } + ] + }, + "nullable": false + } + ], + "inputFields": [ + { + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "KeyFactsItem", + "nullable": false + } + ] + }, + "nullable": false + } + ] + }, + { + "name": "KeyFactsItem", + "fields": [ + { + "name": "icon", + "kind": "Block", + "block": "SvgImage", + "nullable": false + }, + { + "name": "fact", + "kind": "String", + "nullable": false + }, + { + "name": "label", + "kind": "String", + "nullable": false + }, + { + "name": "description", + "kind": "Block", + "block": "RichText", + "nullable": false + } + ], + "inputFields": [ + { + "name": "icon", + "kind": "Block", + "block": "SvgImage", + "nullable": false + }, + { + "name": "fact", + "kind": "String", + "nullable": false + }, + { + "name": "label", + "kind": "String", + "nullable": false + }, + { + "name": "description", + "kind": "Block", + "block": "RichText", + "nullable": false + } + ] + }, { "name": "Layout", "fields": [ @@ -953,12 +1440,12 @@ "name": "props", "kind": "OneOfBlocks", "blocks": { - "internal": "InternalLink", + "damFileDownload": "DamFileDownloadLink", + "email": "EmailLink", "external": "ExternalLink", + "internal": "InternalLink", "news": "NewsLink", - "damFileDownload": "DamFileDownloadLink", - "phone": "PhoneLink", - "email": "EmailLink" + "phone": "PhoneLink" }, "nullable": false } @@ -985,12 +1472,12 @@ "name": "props", "kind": "OneOfBlocks", "blocks": { - "internal": "InternalLink", + "damFileDownload": "DamFileDownloadLink", + "email": "EmailLink", "external": "ExternalLink", + "internal": "InternalLink", "news": "NewsLink", - "damFileDownload": "DamFileDownloadLink", - "phone": "PhoneLink", - "email": "EmailLink" + "phone": "PhoneLink" }, "nullable": false } @@ -1033,7 +1520,44 @@ { "name": "props", "kind": "Block", - "block": "DemoTextLink", + "block": "TextLink", + "nullable": false + }, + { + "name": "userGroup", + "kind": "Enum", + "enum": [ + "All", + "Admin", + "User" + ], + "nullable": false + } + ] + }, + "nullable": false + } + ], + "inputFields": [ + { + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "TextLink", "nullable": false }, { @@ -1050,8 +1574,162 @@ }, "nullable": false } + ] + }, + { + "name": "Media", + "fields": [ + { + "name": "attachedBlocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "type", + "kind": "String", + "nullable": false + }, + { + "name": "props", + "kind": "OneOfBlocks", + "blocks": { + "image": "DamImage", + "damVideo": "DamVideo", + "youTubeVideo": "YouTubeVideo", + "vimeoVideo": "VimeoVideo" + }, + "nullable": false + } + ] + }, + "nullable": false + }, + { + "name": "activeType", + "kind": "String", + "nullable": true + }, + { + "name": "block", + "kind": "NestedObject", + "object": { + "fields": [ + { + "name": "type", + "kind": "String", + "nullable": false + }, + { + "name": "props", + "kind": "OneOfBlocks", + "blocks": { + "image": "DamImage", + "damVideo": "DamVideo", + "youTubeVideo": "YouTubeVideo", + "vimeoVideo": "VimeoVideo" + }, + "nullable": false + } + ] + }, + "nullable": true + } + ], + "inputFields": [ + { + "name": "activeType", + "kind": "String", + "nullable": true + } + ] + }, + { + "name": "MediaGallery", + "fields": [ + { + "name": "items", + "kind": "Block", + "block": "MediaGalleryList", + "nullable": false + }, + { + "name": "aspectRatio", + "kind": "Enum", + "enum": [ + "16x9", + "4x3", + "3x2", + "3x1", + "2x1", + "1x1", + "1x2", + "1x3", + "2x3", + "3x4", + "9x16" + ], + "nullable": false + } + ], + "inputFields": [ + { + "name": "items", + "kind": "Block", + "block": "MediaGalleryList", + "nullable": false + }, + { + "name": "aspectRatio", + "kind": "Enum", + "enum": [ + "16x9", + "4x3", + "3x2", + "3x1", + "2x1", + "1x1", + "1x2", + "1x3", + "2x3", + "3x4", + "9x16" + ], + "nullable": false + } + ] + }, + { + "name": "MediaGalleryItem", + "fields": [ + { + "name": "media", + "kind": "Block", + "block": "Media", + "nullable": false + }, + { + "name": "caption", + "kind": "String", + "nullable": true + } ], "inputFields": [ + { + "name": "media", + "kind": "Block", + "block": "Media", + "nullable": false + }, + { + "name": "caption", + "kind": "String", + "nullable": true + } + ] + }, + { + "name": "MediaGalleryList", + "fields": [ { "name": "blocks", "kind": "NestedObjectList", @@ -1070,89 +1748,39 @@ { "name": "props", "kind": "Block", - "block": "DemoTextLink", - "nullable": false - }, - { - "name": "userGroup", - "kind": "Enum", - "enum": [ - "All", - "Admin", - "User" - ], + "block": "MediaGalleryItem", "nullable": false } ] }, "nullable": false } - ] - }, - { - "name": "Media", - "fields": [ + ], + "inputFields": [ { - "name": "attachedBlocks", + "name": "blocks", "kind": "NestedObjectList", "object": { "fields": [ { - "name": "type", + "name": "key", "kind": "String", "nullable": false }, { - "name": "props", - "kind": "OneOfBlocks", - "blocks": { - "image": "DamImage", - "damVideo": "DamVideo", - "youTubeVideo": "YouTubeVideo", - "vimeoVideo": "VimeoVideo" - }, - "nullable": false - } - ] - }, - "nullable": false - }, - { - "name": "activeType", - "kind": "String", - "nullable": true - }, - { - "name": "block", - "kind": "NestedObject", - "object": { - "fields": [ - { - "name": "type", - "kind": "String", + "name": "visible", + "kind": "Boolean", "nullable": false }, { "name": "props", - "kind": "OneOfBlocks", - "blocks": { - "image": "DamImage", - "damVideo": "DamVideo", - "youTubeVideo": "YouTubeVideo", - "vimeoVideo": "VimeoVideo" - }, + "kind": "Block", + "block": "MediaGalleryItem", "nullable": false } ] }, - "nullable": true - } - ], - "inputFields": [ - { - "name": "activeType", - "kind": "String", - "nullable": true + "nullable": false } ] }, @@ -1183,8 +1811,8 @@ "name": "props", "kind": "OneOfBlocks", "blocks": { - "headline": "Headline", - "richtext": "RichText", + "headline": "Heading", + "richText": "RichText", "image": "DamImage", "textImage": "TextImage" }, @@ -1220,8 +1848,8 @@ "name": "props", "kind": "OneOfBlocks", "blocks": { - "headline": "Headline", - "richtext": "RichText", + "headline": "Heading", + "richText": "RichText", "image": "DamImage", "textImage": "TextImage" }, @@ -1409,22 +2037,23 @@ "name": "props", "kind": "OneOfBlocks", "blocks": { - "space": "DemoSpace", - "richtext": "RichText", - "headline": "Headline", + "anchor": "Anchor", + "billboardTeaser": "BillboardTeaser", + "columns": "Columns", + "contentGroup": "ContentGroup", + "fullWidthImage": "FullWidthImage", + "heading": "Heading", "image": "DamImage", - "textImage": "TextImage", + "imageLink": "ImageLink", + "layout": "Layout", "linkList": "LinkList", - "fullWidthImage": "FullWidthImage", - "columns": "Columns", - "anchor": "Anchor", - "twoLists": "TwoLists", "media": "Media", - "teaser": "Teaser", "newsDetail": "NewsDetail", - "imageLink": "ImageLink", "newsList": "NewsList", - "layout": "Layout" + "richText": "RichText", + "space": "Space", + "teaser": "Teaser", + "textImage": "TextImage" }, "nullable": false }, @@ -1468,22 +2097,23 @@ "name": "props", "kind": "OneOfBlocks", "blocks": { - "space": "DemoSpace", - "richtext": "RichText", - "headline": "Headline", + "anchor": "Anchor", + "billboardTeaser": "BillboardTeaser", + "columns": "Columns", + "contentGroup": "ContentGroup", + "fullWidthImage": "FullWidthImage", + "heading": "Heading", "image": "DamImage", - "textImage": "TextImage", + "imageLink": "ImageLink", + "layout": "Layout", "linkList": "LinkList", - "fullWidthImage": "FullWidthImage", - "columns": "Columns", - "anchor": "Anchor", - "twoLists": "TwoLists", "media": "Media", - "teaser": "Teaser", "newsDetail": "NewsDetail", - "imageLink": "ImageLink", "newsList": "NewsList", - "layout": "Layout" + "richText": "RichText", + "space": "Space", + "teaser": "Teaser", + "textImage": "TextImage" }, "nullable": false }, @@ -1946,92 +2576,321 @@ "nullable": true }, { - "name": "openGraphImage", + "name": "openGraphImage", + "kind": "Block", + "block": "OptionalPixelImage", + "nullable": false + }, + { + "name": "structuredData", + "kind": "String", + "nullable": true + }, + { + "name": "noIndex", + "kind": "Boolean", + "nullable": false + }, + { + "name": "priority", + "kind": "Enum", + "enum": [ + "0_0", + "0_1", + "0_2", + "0_3", + "0_4", + "0_5", + "0_6", + "0_7", + "0_8", + "0_9", + "1_0" + ], + "nullable": false + }, + { + "name": "changeFrequency", + "kind": "Enum", + "enum": [ + "always", + "hourly", + "daily", + "weekly", + "monthly", + "yearly", + "never" + ], + "nullable": false + }, + { + "name": "canonicalUrl", + "kind": "String", + "nullable": true + }, + { + "name": "alternativeLinks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "code", + "kind": "String", + "nullable": true + }, + { + "name": "url", + "kind": "String", + "nullable": true + } + ] + }, + "nullable": false + } + ] + }, + { + "name": "Space", + "fields": [ + { + "name": "height", + "kind": "Number", + "nullable": false + } + ], + "inputFields": [ + { + "name": "height", + "kind": "Number", + "nullable": false + } + ] + }, + { + "name": "Space", + "fields": [ + { + "name": "spacing", + "kind": "Enum", + "enum": [ + "D100", + "D200", + "D300", + "D400", + "S100", + "S200", + "S300", + "S400", + "S500", + "S600" + ], + "nullable": false + } + ], + "inputFields": [ + { + "name": "spacing", + "kind": "Enum", + "enum": [ + "D100", + "D200", + "D300", + "D400", + "S100", + "S200", + "S300", + "S400", + "S500", + "S600" + ], + "nullable": false + } + ] + }, + { + "name": "Stage", + "fields": [ + { + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "BasicStage", + "nullable": false + } + ] + }, + "nullable": false + } + ], + "inputFields": [ + { + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "BasicStage", + "nullable": false + } + ] + }, + "nullable": false + } + ] + }, + { + "name": "StandaloneCallToActionList", + "fields": [ + { + "name": "callToActionList", "kind": "Block", - "block": "OptionalPixelImage", + "block": "CallToActionList", "nullable": false }, { - "name": "structuredData", - "kind": "String", - "nullable": true - }, + "name": "alignment", + "kind": "Enum", + "enum": [ + "left", + "center", + "right" + ], + "nullable": false + } + ], + "inputFields": [ { - "name": "noIndex", - "kind": "Boolean", + "name": "callToActionList", + "kind": "Block", + "block": "CallToActionList", "nullable": false }, { - "name": "priority", + "name": "alignment", "kind": "Enum", "enum": [ - "0_0", - "0_1", - "0_2", - "0_3", - "0_4", - "0_5", - "0_6", - "0_7", - "0_8", - "0_9", - "1_0" + "left", + "center", + "right" ], "nullable": false + } + ] + }, + { + "name": "StandaloneHeading", + "fields": [ + { + "name": "heading", + "kind": "Block", + "block": "Heading", + "nullable": false }, { - "name": "changeFrequency", + "name": "textAlignment", "kind": "Enum", "enum": [ - "always", - "hourly", - "daily", - "weekly", - "monthly", - "yearly", - "never" + "left", + "center" ], "nullable": false - }, + } + ], + "inputFields": [ { - "name": "canonicalUrl", - "kind": "String", - "nullable": true + "name": "heading", + "kind": "Block", + "block": "Heading", + "nullable": false }, { - "name": "alternativeLinks", - "kind": "NestedObjectList", - "object": { - "fields": [ - { - "name": "code", - "kind": "String", - "nullable": true - }, - { - "name": "url", - "kind": "String", - "nullable": true - } - ] - }, + "name": "textAlignment", + "kind": "Enum", + "enum": [ + "left", + "center" + ], "nullable": false } ] }, { - "name": "Space", + "name": "StandaloneMedia", "fields": [ { - "name": "height", - "kind": "Number", + "name": "media", + "kind": "Block", + "block": "Media", + "nullable": false + }, + { + "name": "aspectRatio", + "kind": "Enum", + "enum": [ + "16x9", + "4x3", + "3x2", + "3x1", + "2x1", + "1x1", + "1x2", + "1x3", + "2x3", + "3x4", + "9x16" + ], "nullable": false } ], "inputFields": [ { - "name": "height", - "kind": "Number", + "name": "media", + "kind": "Block", + "block": "Media", + "nullable": false + }, + { + "name": "aspectRatio", + "kind": "Enum", + "enum": [ + "16x9", + "4x3", + "3x2", + "3x1", + "2x1", + "1x1", + "1x2", + "1x3", + "2x3", + "3x4", + "9x16" + ], "nullable": false } ] @@ -2111,65 +2970,108 @@ "name": "Teaser", "fields": [ { - "name": "headline", - "kind": "Block", - "block": "Headline", + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "TeaserItem", + "nullable": false + } + ] + }, "nullable": false - }, + } + ], + "inputFields": [ { - "name": "image", + "name": "blocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "key", + "kind": "String", + "nullable": false + }, + { + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "TeaserItem", + "nullable": false + } + ] + }, + "nullable": false + } + ] + }, + { + "name": "TeaserItem", + "fields": [ + { + "name": "media", "kind": "Block", - "block": "DamImage", + "block": "Media", "nullable": false }, { - "name": "links", - "kind": "Block", - "block": "LinkList", + "name": "title", + "kind": "String", "nullable": false }, { - "name": "buttons", + "name": "description", "kind": "Block", - "block": "LinkList", + "block": "RichText", "nullable": false }, { - "name": "columns", + "name": "link", "kind": "Block", - "block": "Columns", + "block": "TextLink", "nullable": false } ], "inputFields": [ { - "name": "headline", - "kind": "Block", - "block": "Headline", - "nullable": false - }, - { - "name": "image", + "name": "media", "kind": "Block", - "block": "DamImage", + "block": "Media", "nullable": false }, { - "name": "links", - "kind": "Block", - "block": "LinkList", + "name": "title", + "kind": "String", "nullable": false }, { - "name": "buttons", + "name": "description", "kind": "Block", - "block": "LinkList", + "block": "RichText", "nullable": false }, { - "name": "columns", + "name": "link", "kind": "Block", - "block": "Columns", + "block": "TextLink", "nullable": false } ] @@ -2234,89 +3136,30 @@ ] }, { - "name": "TwoLists", + "name": "TextLink", "fields": [ { - "name": "list1", - "kind": "Block", - "block": "TwoListsList", + "name": "text", + "kind": "String", "nullable": false }, { - "name": "list2", + "name": "link", "kind": "Block", - "block": "TwoListsList", + "block": "Link", "nullable": false } ], "inputFields": [ { - "name": "list1", - "kind": "Block", - "block": "TwoListsList", + "name": "text", + "kind": "String", "nullable": false }, { - "name": "list2", + "name": "link", "kind": "Block", - "block": "TwoListsList", - "nullable": false - } - ] - }, - { - "name": "TwoListsList", - "fields": [ - { - "name": "blocks", - "kind": "NestedObjectList", - "object": { - "fields": [ - { - "name": "key", - "kind": "String", - "nullable": false - }, - { - "name": "visible", - "kind": "Boolean", - "nullable": false - }, - { - "name": "props", - "kind": "Block", - "block": "Headline", - "nullable": false - } - ] - }, - "nullable": false - } - ], - "inputFields": [ - { - "name": "blocks", - "kind": "NestedObjectList", - "object": { - "fields": [ - { - "name": "key", - "kind": "String", - "nullable": false - }, - { - "name": "visible", - "kind": "Boolean", - "nullable": false - }, - { - "name": "props", - "kind": "Block", - "block": "Headline", - "nullable": false - } - ] - }, + "block": "Link", "nullable": false } ] diff --git a/demo/api/package.json b/demo/api/package.json index d99ac41699..b72c67dc96 100644 --- a/demo/api/package.json +++ b/demo/api/package.json @@ -6,7 +6,7 @@ "api-generator": "rimraf 'src/*/generated' && comet-api-generator generate", "prebuild": "rimraf dist", "build": "nest build", - "console": "dotenv -c -e .env.site-configs -- ts-node --transpile-only -r tsconfig-paths/register src/console.ts", + "console": "dotenv -e .env.secrets -e .env.local -e .env -e .env.site-configs -- ts-node --transpile-only -r tsconfig-paths/register src/console.ts", "console:prod": "node dist/console.js", "db:migrate": "$npm_execpath console migrate --", "db:migrate:prod": "$npm_execpath console:prod migrate --", diff --git a/demo/api/schema.gql b/demo/api/schema.gql index e25e197c96..d28ce29d9c 100644 --- a/demo/api/schema.gql +++ b/demo/api/schema.gql @@ -225,6 +225,7 @@ type Page implements DocumentInterface { updatedAt: DateTime! content: PageContentBlockData! seo: SeoBlockData! + stage: StageBlockData! createdAt: DateTime! pageTreeNode: PageTreeNode dependencies(offset: Int! = 0, limit: Int! = 25, filter: DependencyFilter, forceRefresh: Boolean! = false): PaginatedDependencies! @@ -236,6 +237,9 @@ scalar PageContentBlockData @specifiedBy(url: "http://www.ecma-international.org """Seo root block data""" scalar SeoBlockData @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") +"""Stage root block data""" +scalar StageBlockData @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") + input DependencyFilter { targetGraphqlObjectType: String targetId: String @@ -347,7 +351,7 @@ enum PredefinedPageType { News } -type FooterContentScope { +type FooterScope { domain: String! language: String! } @@ -355,7 +359,7 @@ type FooterContentScope { type Footer { id: ID! content: FooterContentBlockData! - scope: FooterContentScope! + scope: FooterScope! createdAt: DateTime! updatedAt: DateTime! dependencies(offset: Int! = 0, limit: Int! = 25, filter: DependencyFilter, forceRefresh: Boolean! = false): PaginatedDependencies! @@ -691,7 +695,7 @@ input DamScopeInput { domain: String! } -input FooterContentScopeInput { +input FooterScopeInput { domain: String! language: String! } @@ -794,7 +798,7 @@ type Query { mainMenu(scope: PageTreeNodeScopeInput!): MainMenu! topMenu(scope: PageTreeNodeScopeInput!): [PageTreeNode!]! mainMenuItem(pageTreeNodeId: ID!): MainMenuItem! - footer(scope: FooterContentScopeInput!): Footer + footer(scope: FooterScopeInput!): Footer predefinedPage(id: ID!): PredefinedPage! kubernetesCronJobs: [KubernetesCronJob!]! kubernetesCronJob(name: String!): KubernetesCronJob! @@ -1218,7 +1222,7 @@ type Mutation { updateNewsComment(id: ID!, input: NewsCommentInput!): NewsComment! deleteNewsComment(id: ID!): Boolean! updateMainMenuItem(pageTreeNodeId: ID!, input: MainMenuItemInput!, lastUpdatedAt: DateTime): MainMenuItem! - saveFooter(scope: FooterContentScopeInput!, input: FooterInput!): Footer! + saveFooter(scope: FooterScopeInput!, input: FooterInput!): Footer! savePredefinedPage(id: ID!, input: PredefinedPageInput!, attachedPageTreeNodeId: ID!, lastUpdatedAt: DateTime): PredefinedPage! triggerKubernetesCronJob(name: String!): KubernetesJob! createProduct(input: ProductInput!): Product! @@ -1271,6 +1275,7 @@ scalar LinkBlockInput @specifiedBy(url: "http://www.ecma-international.org/publi input PageInput { content: PageContentBlockInput! seo: SeoBlockInput! + stage: StageBlockInput! } """PageContent root block input""" @@ -1279,6 +1284,9 @@ scalar PageContentBlockInput @specifiedBy(url: "http://www.ecma-international.or """Seo root block input""" scalar SeoBlockInput @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") +"""Stage root block input""" +scalar StageBlockInput @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") + input PageTreeNodeUpdateInput { name: String! slug: String! diff --git a/demo/api/src/app.module.ts b/demo/api/src/app.module.ts index 6429fc284a..50b93559a0 100644 --- a/demo/api/src/app.module.ts +++ b/demo/api/src/app.module.ts @@ -25,8 +25,8 @@ import { Config } from "@src/config/config"; import { ConfigModule } from "@src/config/config.module"; import { ContentGenerationService } from "@src/content-generation/content-generation.service"; import { DbModule } from "@src/db/db.module"; -import { LinksModule } from "@src/links/links.module"; -import { PagesModule } from "@src/pages/pages.module"; +import { LinksModule } from "@src/documents/links/links.module"; +import { PagesModule } from "@src/documents/pages/pages.module"; import { ValidationError } from "apollo-server-express"; import { Request } from "express"; @@ -36,10 +36,11 @@ import { UserService } from "./auth/user.service"; import { DamScope } from "./dam/dto/dam-scope"; import { DamFile } from "./dam/entities/dam-file.entity"; import { DamFolder } from "./dam/entities/dam-folder.entity"; +import { Link } from "./documents/links/entities/link.entity"; +import { Page } from "./documents/pages/entities/page.entity"; import { PredefinedPage } from "./documents/predefined-pages/entities/predefined-page.entity"; import { PredefinedPagesModule } from "./documents/predefined-pages/predefined-pages.module"; import { FooterModule } from "./footer/footer.module"; -import { Link } from "./links/entities/link.entity"; import { MenusModule } from "./menus/menus.module"; import { NewsLinkBlock } from "./news/blocks/news-link.block"; import { NewsModule } from "./news/news.module"; @@ -47,7 +48,6 @@ import { OpenTelemetryModule } from "./open-telemetry/open-telemetry.module"; import { PageTreeNodeCreateInput, PageTreeNodeUpdateInput } from "./page-tree/dto/page-tree-node.input"; import { PageTreeNodeScope } from "./page-tree/dto/page-tree-node-scope"; import { PageTreeNode } from "./page-tree/entities/page-tree-node.entity"; -import { Page } from "./pages/entities/page.entity"; import { ProductsModule } from "./products/products.module"; import { RedirectScope } from "./redirects/dto/redirect-scope"; diff --git a/demo/api/src/comet-config.json b/demo/api/src/comet-config.json index 5a88b97cf7..76beb92c52 100644 --- a/demo/api/src/comet-config.json +++ b/demo/api/src/comet-config.json @@ -1,14 +1,14 @@ { + "dam": { + "allowedImageAspectRatios": ["16x9", "4x3", "3x2", "3x1", "2x1", "1x1", "1x2", "1x3", "2x3", "3x4", "9x16", "1200x630"], + "allowedImageSizes": [320, 420, 768, 1024, 1200, 2048], + "uploadsMaxFileSize": 500 + }, "fileUploads": { "maxFileSize": 15 }, "imgproxy": { "maxSrcResolution": 70, "quality": 80 - }, - "dam": { - "uploadsMaxFileSize": 500, - "allowedImageSizes": [320, 420, 768, 1024, 1200, 2048], - "allowedImageAspectRatios": ["16x9", "4x3", "3x2", "3x1", "2x1", "1x1", "1x2", "1x3", "2x3", "3x4", "9x16", "1200x630"] } } diff --git a/demo/api/src/common/blocks/accordion-item.block.ts b/demo/api/src/common/blocks/accordion-item.block.ts new file mode 100644 index 0000000000..d6bd51d5f5 --- /dev/null +++ b/demo/api/src/common/blocks/accordion-item.block.ts @@ -0,0 +1,61 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + createBlocksBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { IsUndefinable } from "@comet/cms-api"; +import { RichTextBlock } from "@src/common/blocks/rich-text.block"; +import { SpaceBlock } from "@src/common/blocks/space.block"; +import { StandaloneCallToActionListBlock } from "@src/common/blocks/standalone-call-to-action-list.block"; +import { StandaloneHeadingBlock } from "@src/common/blocks/standalone-heading.block"; +import { IsBoolean, IsString } from "class-validator"; + +const AccordionContentBlock = createBlocksBlock( + { + supportedBlocks: { + richText: RichTextBlock, + heading: StandaloneHeadingBlock, + space: SpaceBlock, + callToActionList: StandaloneCallToActionListBlock, + }, + }, + "AccordionContent", +); + +class AccordionItemBlockData extends BlockData { + @BlockField({ nullable: true }) + title?: string; + + @ChildBlock(AccordionContentBlock) + content: BlockDataInterface; + + @BlockField() + openByDefault: boolean; +} + +class AccordionItemBlockInput extends BlockInput { + @IsUndefinable() + @BlockField({ nullable: true }) + @IsString() + title?: string; + + @ChildBlockInput(AccordionContentBlock) + content: ExtractBlockInput; + + @IsBoolean() + @BlockField() + openByDefault: boolean; + + transformToBlockData(): AccordionItemBlockData { + return inputToData(AccordionItemBlockData, this); + } +} + +export const AccordionItemBlock = createBlock(AccordionItemBlockData, AccordionItemBlockInput, "AccordionItem"); diff --git a/demo/api/src/common/blocks/accordion.block.ts b/demo/api/src/common/blocks/accordion.block.ts new file mode 100644 index 0000000000..8c093506a3 --- /dev/null +++ b/demo/api/src/common/blocks/accordion.block.ts @@ -0,0 +1,4 @@ +import { createListBlock } from "@comet/blocks-api"; +import { AccordionItemBlock } from "@src/common/blocks/accordion-item.block"; + +export const AccordionBlock = createListBlock({ block: AccordionItemBlock }, "Accordion"); diff --git a/demo/api/src/common/blocks/call-to-action-list.block.ts b/demo/api/src/common/blocks/call-to-action-list.block.ts new file mode 100644 index 0000000000..8d4a05d95f --- /dev/null +++ b/demo/api/src/common/blocks/call-to-action-list.block.ts @@ -0,0 +1,4 @@ +import { createListBlock } from "@comet/blocks-api"; +import { CallToActionBlock } from "@src/common/blocks/call-to-action.block"; + +export const CallToActionListBlock = createListBlock({ block: CallToActionBlock }, "CallToActionList"); diff --git a/demo/api/src/common/blocks/call-to-action.block.ts b/demo/api/src/common/blocks/call-to-action.block.ts new file mode 100644 index 0000000000..9f147ab38b --- /dev/null +++ b/demo/api/src/common/blocks/call-to-action.block.ts @@ -0,0 +1,43 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { IsEnum } from "class-validator"; + +import { TextLinkBlock } from "./text-link.block"; + +enum Variant { + Contained = "Contained", + Outlined = "Outlined", + Text = "Text", +} + +class CallToActionBlockData extends BlockData { + @ChildBlock(TextLinkBlock) + textLink: BlockDataInterface; + + @BlockField({ type: "enum", enum: Variant }) + variant: Variant; +} + +class CallToActionBlockInput extends BlockInput { + @ChildBlockInput(TextLinkBlock) + textLink: ExtractBlockInput; + + @IsEnum(Variant) + @BlockField({ type: "enum", enum: Variant }) + variant: Variant; + + transformToBlockData(): CallToActionBlockData { + return inputToData(CallToActionBlockData, this); + } +} + +export const CallToActionBlock = createBlock(CallToActionBlockData, CallToActionBlockInput, "CallToAction"); diff --git a/demo/api/src/common/blocks/heading.block.ts b/demo/api/src/common/blocks/heading.block.ts new file mode 100644 index 0000000000..6061ca48e9 --- /dev/null +++ b/demo/api/src/common/blocks/heading.block.ts @@ -0,0 +1,52 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { IsEnum } from "class-validator"; + +import { RichTextBlock } from "./rich-text.block"; + +enum HeadlineTag { + H1 = "H1", + H2 = "H2", + H3 = "H3", + H4 = "H4", + H5 = "H5", + H6 = "H6", +} + +class HeadingBlockData extends BlockData { + @ChildBlock(RichTextBlock) + eyebrow: BlockDataInterface; + + @ChildBlock(RichTextBlock) + headline: BlockDataInterface; + + @BlockField({ type: "enum", enum: HeadlineTag }) + htmlTag: HeadlineTag; +} + +class HeadingBlockInput extends BlockInput { + @ChildBlockInput(RichTextBlock) + eyebrow: ExtractBlockInput; + + @ChildBlockInput(RichTextBlock) + headline: ExtractBlockInput; + + @IsEnum(HeadlineTag) + @BlockField({ type: "enum", enum: HeadlineTag }) + htmlTag: HeadlineTag; + + transformToBlockData(): HeadingBlockData { + return inputToData(HeadingBlockData, this); + } +} + +export const HeadingBlock = createBlock(HeadingBlockData, HeadingBlockInput, "Heading"); diff --git a/demo/api/src/common/blocks/linkBlock/link.block.ts b/demo/api/src/common/blocks/link.block.ts similarity index 100% rename from demo/api/src/common/blocks/linkBlock/link.block.ts rename to demo/api/src/common/blocks/link.block.ts index 1e1aaa320a..cfab83e4fc 100644 --- a/demo/api/src/common/blocks/linkBlock/link.block.ts +++ b/demo/api/src/common/blocks/link.block.ts @@ -4,11 +4,11 @@ import { NewsLinkBlock } from "@src/news/blocks/news-link.block"; export const LinkBlock = createLinkBlock({ supportedBlocks: { - internal: InternalLinkBlock, + damFileDownload: DamFileDownloadLinkBlock, + email: EmailLinkBlock, external: ExternalLinkBlock, + internal: InternalLinkBlock, news: NewsLinkBlock, - damFileDownload: DamFileDownloadLinkBlock, phone: PhoneLinkBlock, - email: EmailLinkBlock, }, }); diff --git a/demo/api/src/common/blocks/media-gallery-item.block.ts b/demo/api/src/common/blocks/media-gallery-item.block.ts new file mode 100644 index 0000000000..6d2241ae1c --- /dev/null +++ b/demo/api/src/common/blocks/media-gallery-item.block.ts @@ -0,0 +1,38 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { IsUndefinable } from "@comet/cms-api"; +import { MediaBlock } from "@src/common/blocks/media.block"; +import { IsString } from "class-validator"; + +class MediaGalleryItemBlockData extends BlockData { + @ChildBlock(MediaBlock) + media: BlockDataInterface; + + @BlockField({ nullable: true }) + caption?: string; +} + +class MediaGalleryItemBlockInput extends BlockInput { + @ChildBlockInput(MediaBlock) + media: ExtractBlockInput; + + @IsUndefinable() + @BlockField({ nullable: true }) + @IsString() + caption?: string; + + transformToBlockData(): MediaGalleryItemBlockData { + return inputToData(MediaGalleryItemBlockData, this); + } +} + +export const MediaGalleryItemBlock = createBlock(MediaGalleryItemBlockData, MediaGalleryItemBlockInput, "MediaGalleryItem"); diff --git a/demo/api/src/common/blocks/media-gallery.block.ts b/demo/api/src/common/blocks/media-gallery.block.ts new file mode 100644 index 0000000000..986fece41a --- /dev/null +++ b/demo/api/src/common/blocks/media-gallery.block.ts @@ -0,0 +1,40 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + createListBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { MediaGalleryItemBlock } from "@src/common/blocks/media-gallery-item.block"; +import { MediaAspectRatios } from "@src/util/mediaAspectRatios"; +import { IsEnum } from "class-validator"; + +const MediaGalleryListBlock = createListBlock({ block: MediaGalleryItemBlock }, "MediaGalleryList"); + +class MediaGalleryBlockData extends BlockData { + @ChildBlock(MediaGalleryListBlock) + items: BlockDataInterface; + + @BlockField({ type: "enum", enum: MediaAspectRatios }) + aspectRatio: MediaAspectRatios; +} + +class MediaGalleryBlockInput extends BlockInput { + @ChildBlockInput(MediaGalleryListBlock) + items: ExtractBlockInput; + + @IsEnum(MediaAspectRatios) + @BlockField({ type: "enum", enum: MediaAspectRatios }) + aspectRatio: MediaAspectRatios; + + transformToBlockData(): MediaGalleryBlockData { + return inputToData(MediaGalleryBlockData, this); + } +} + +export const MediaGalleryBlock = createBlock(MediaGalleryBlockData, MediaGalleryBlockInput, "MediaGallery"); diff --git a/demo/api/src/pages/blocks/media.block.ts b/demo/api/src/common/blocks/media.block.ts similarity index 53% rename from demo/api/src/pages/blocks/media.block.ts rename to demo/api/src/common/blocks/media.block.ts index dd416ef933..b70580744b 100644 --- a/demo/api/src/pages/blocks/media.block.ts +++ b/demo/api/src/common/blocks/media.block.ts @@ -3,7 +3,12 @@ import { DamImageBlock, DamVideoBlock, VimeoVideoBlock, YouTubeVideoBlock } from export const MediaBlock = createOneOfBlock( { - supportedBlocks: { image: DamImageBlock, damVideo: DamVideoBlock, youTubeVideo: YouTubeVideoBlock, vimeoVideo: VimeoVideoBlock }, + supportedBlocks: { + image: DamImageBlock, + damVideo: DamVideoBlock, + youTubeVideo: YouTubeVideoBlock, + vimeoVideo: VimeoVideoBlock, + }, }, "Media", ); diff --git a/demo/api/src/common/blocks/rich-text.block.ts b/demo/api/src/common/blocks/rich-text.block.ts index 81a1cd9fb7..8c9a2086b1 100644 --- a/demo/api/src/common/blocks/rich-text.block.ts +++ b/demo/api/src/common/blocks/rich-text.block.ts @@ -1,5 +1,4 @@ import { createRichTextBlock } from "@comet/blocks-api"; - -import { LinkBlock } from "./linkBlock/link.block"; +import { LinkBlock } from "@src/common/blocks/link.block"; export const RichTextBlock = createRichTextBlock({ link: LinkBlock }); diff --git a/demo/api/src/common/blocks/space.block.ts b/demo/api/src/common/blocks/space.block.ts index 13919f1d01..c56ade8485 100644 --- a/demo/api/src/common/blocks/space.block.ts +++ b/demo/api/src/common/blocks/space.block.ts @@ -1,16 +1,16 @@ import { createSpaceBlock } from "@comet/blocks-api"; export enum Spacing { - d150 = "d150", - d200 = "d200", - d250 = "d250", - d300 = "d300", - d350 = "d350", - d400 = "d400", - d450 = "d450", - d500 = "d500", - d550 = "d550", - d600 = "d600", + D100 = "D100", + D200 = "D200", + D300 = "D300", + D400 = "D400", + S100 = "S100", + S200 = "S200", + S300 = "S300", + S400 = "S400", + S500 = "S500", + S600 = "S600", } -export const SpaceBlock = createSpaceBlock({ spacing: Spacing }, "DemoSpace"); +export const SpaceBlock = createSpaceBlock({ spacing: Spacing }); diff --git a/demo/api/src/common/blocks/standalone-call-to-action-list.block.ts b/demo/api/src/common/blocks/standalone-call-to-action-list.block.ts new file mode 100644 index 0000000000..d947f96536 --- /dev/null +++ b/demo/api/src/common/blocks/standalone-call-to-action-list.block.ts @@ -0,0 +1,44 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { CallToActionListBlock } from "@src/common/blocks/call-to-action-list.block"; +import { IsEnum } from "class-validator"; + +enum Alignment { + left = "left", + center = "center", + right = "right", +} + +class StandaloneCallToActionListBlockData extends BlockData { + @ChildBlock(CallToActionListBlock) + callToActionList: BlockDataInterface; + + @BlockField({ type: "enum", enum: Alignment }) + alignment: Alignment; +} + +class StandaloneCallToActionListBlockInput extends BlockInput { + @ChildBlockInput(CallToActionListBlock) + callToActionList: ExtractBlockInput; + + @IsEnum(Alignment) + @BlockField({ type: "enum", enum: Alignment }) + alignment: Alignment; + + transformToBlockData(): StandaloneCallToActionListBlockData { + return inputToData(StandaloneCallToActionListBlockData, this); + } +} + +export const StandaloneCallToActionListBlock = createBlock(StandaloneCallToActionListBlockData, StandaloneCallToActionListBlockInput, { + name: "StandaloneCallToActionList", +}); diff --git a/demo/api/src/common/blocks/standalone-heading.block.ts b/demo/api/src/common/blocks/standalone-heading.block.ts new file mode 100644 index 0000000000..92d4b33e13 --- /dev/null +++ b/demo/api/src/common/blocks/standalone-heading.block.ts @@ -0,0 +1,43 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { HeadingBlock } from "@src/common/blocks/heading.block"; +import { IsEnum } from "class-validator"; + +enum TextAlignment { + left = "left", + center = "center", +} + +class StandaloneHeadingBlockData extends BlockData { + @ChildBlock(HeadingBlock) + heading: BlockDataInterface; + + @BlockField({ type: "enum", enum: TextAlignment }) + textAlignment: TextAlignment; +} + +class StandaloneHeadingBlockInput extends BlockInput { + @ChildBlockInput(HeadingBlock) + heading: ExtractBlockInput; + + @IsEnum(TextAlignment) + @BlockField({ type: "enum", enum: TextAlignment }) + textAlignment: TextAlignment; + + transformToBlockData(): StandaloneHeadingBlockData { + return inputToData(StandaloneHeadingBlockData, this); + } +} + +export const StandaloneHeadingBlock = createBlock(StandaloneHeadingBlockData, StandaloneHeadingBlockInput, { + name: "StandaloneHeading", +}); diff --git a/demo/api/src/common/blocks/standalone-media.block.ts b/demo/api/src/common/blocks/standalone-media.block.ts new file mode 100644 index 0000000000..60f16c14a0 --- /dev/null +++ b/demo/api/src/common/blocks/standalone-media.block.ts @@ -0,0 +1,39 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { MediaBlock } from "@src/common/blocks/media.block"; +import { MediaAspectRatios } from "@src/util/mediaAspectRatios"; +import { IsEnum } from "class-validator"; + +class StandaloneMediaBlockData extends BlockData { + @ChildBlock(MediaBlock) + media: BlockDataInterface; + + @BlockField({ type: "enum", enum: MediaAspectRatios }) + aspectRatio: MediaAspectRatios; +} + +class StandaloneMediaBlockInput extends BlockInput { + @ChildBlockInput(MediaBlock) + media: ExtractBlockInput; + + @IsEnum(MediaAspectRatios) + @BlockField({ type: "enum", enum: MediaAspectRatios }) + aspectRatio: MediaAspectRatios; + + transformToBlockData(): StandaloneMediaBlockData { + return inputToData(StandaloneMediaBlockData, this); + } +} + +export const StandaloneMediaBlock = createBlock(StandaloneMediaBlockData, StandaloneMediaBlockInput, { + name: "StandaloneMedia", +}); diff --git a/demo/api/src/common/blocks/text-link.block.ts b/demo/api/src/common/blocks/text-link.block.ts index 6cbe812a91..34c0af20a0 100644 --- a/demo/api/src/common/blocks/text-link.block.ts +++ b/demo/api/src/common/blocks/text-link.block.ts @@ -1,5 +1,4 @@ import { createTextLinkBlock } from "@comet/blocks-api"; +import { LinkBlock } from "@src/common/blocks/link.block"; -import { LinkBlock } from "./linkBlock/link.block"; - -export const TextLinkBlock = createTextLinkBlock({ link: LinkBlock }, "DemoTextLink"); +export const TextLinkBlock = createTextLinkBlock({ link: LinkBlock }); diff --git a/demo/api/src/db/fixtures/fixtures.console.ts b/demo/api/src/db/fixtures/fixtures.console.ts index 7249befe7f..97a1e643d9 100644 --- a/demo/api/src/db/fixtures/fixtures.console.ts +++ b/demo/api/src/db/fixtures/fixtures.console.ts @@ -6,12 +6,12 @@ import { Inject, Injectable } from "@nestjs/common"; import { Config } from "@src/config/config"; import { CONFIG } from "@src/config/config.module"; import { generateSeoBlock } from "@src/db/fixtures/generators/blocks/seo.generator"; -import { Link } from "@src/links/entities/link.entity"; +import { Link } from "@src/documents/links/entities/link.entity"; +import { PageContentBlock } from "@src/documents/pages/blocks/page-content.block"; +import { PageInput } from "@src/documents/pages/dto/page.input"; +import { Page } from "@src/documents/pages/entities/page.entity"; import { PageTreeNodeScope } from "@src/page-tree/dto/page-tree-node-scope"; import { PageTreeNodeCategory } from "@src/page-tree/page-tree-node-category"; -import { PageContentBlock } from "@src/pages/blocks/page-content.block"; -import { PageInput } from "@src/pages/dto/page.input"; -import { Page } from "@src/pages/entities/page.entity"; import { UserGroup } from "@src/user-groups/user-group"; import faker from "faker"; import { Command, Console } from "nestjs-console"; @@ -209,6 +209,7 @@ export class FixturesConsole { updatedAt: new Date(), content: pageInput.content.transformToBlockData(), seo: pageInput.seo.transformToBlockData(), + stage: pageInput.stage.transformToBlockData(), }), ); } @@ -261,6 +262,7 @@ export class FixturesConsole { id: pageId, content: pageInput.content.transformToBlockData(), seo: pageInput.seo.transformToBlockData(), + stage: pageInput.stage.transformToBlockData(), }), ); await this.pageTreeService.attachDocument({ id: pageId, type: "Page" }, page.id); diff --git a/demo/api/src/db/fixtures/fixtures.module.ts b/demo/api/src/db/fixtures/fixtures.module.ts index e98de8026d..97ca35a6d7 100644 --- a/demo/api/src/db/fixtures/fixtures.module.ts +++ b/demo/api/src/db/fixtures/fixtures.module.ts @@ -2,8 +2,8 @@ import { DependenciesModule } from "@comet/cms-api"; import { Module } from "@nestjs/common"; import { ConfigModule } from "@src/config/config.module"; import { FixturesConsole } from "@src/db/fixtures/fixtures.console"; -import { LinksModule } from "@src/links/links.module"; -import { PagesModule } from "@src/pages/pages.module"; +import { LinksModule } from "@src/documents/links/links.module"; +import { PagesModule } from "@src/documents/pages/pages.module"; import { ConsoleModule } from "nestjs-console"; import { FileUploadsFixtureService } from "./generators/file-uploads-fixture.service"; diff --git a/demo/api/src/db/fixtures/generators/blocks/blocks.generator.ts b/demo/api/src/db/fixtures/generators/blocks/blocks.generator.ts index 42fe607046..7faa3e82de 100644 --- a/demo/api/src/db/fixtures/generators/blocks/blocks.generator.ts +++ b/demo/api/src/db/fixtures/generators/blocks/blocks.generator.ts @@ -1,7 +1,7 @@ import { BlocksBlockFixturesGeneratorMap, ExtractBlockInput, ExtractBlockInputFactoryProps } from "@comet/blocks-api"; import { FileInterface } from "@comet/cms-api"; import { Config } from "@src/config/config"; -import { PageContentBlock } from "@src/pages/blocks/page-content.block"; +import { PageContentBlock } from "@src/documents/pages/blocks/page-content.block"; import faker from "faker"; import { generateImageBlock } from "./image.generator"; @@ -14,7 +14,7 @@ export const generateBlocksBlock = ( config: Config, blockCfg: Partial> = { space: generateSpaceBlock, - richtext: generateRichtextBlock, + richText: generateRichtextBlock, image: () => generateImageBlock(imageFiles), textImage: () => generateTextImageBlock(imageFiles, config), }, diff --git a/demo/api/src/db/fixtures/generators/blocks/seo.generator.ts b/demo/api/src/db/fixtures/generators/blocks/seo.generator.ts index ab307b569e..61df357ae6 100644 --- a/demo/api/src/db/fixtures/generators/blocks/seo.generator.ts +++ b/demo/api/src/db/fixtures/generators/blocks/seo.generator.ts @@ -1,6 +1,6 @@ import { BlockInputInterface } from "@comet/blocks-api"; import { SitemapPageChangeFrequency, SitemapPagePriority } from "@comet/cms-api"; -import { SeoBlock } from "@src/pages/blocks/seo.block"; +import { SeoBlock } from "@src/documents/pages/blocks/seo.block"; export const generateSeoBlock = (): BlockInputInterface => { // @TODO Introduce randomness diff --git a/demo/api/src/db/fixtures/generators/blocks/space.generator.ts b/demo/api/src/db/fixtures/generators/blocks/space.generator.ts index a83c17b228..c02583f19f 100644 --- a/demo/api/src/db/fixtures/generators/blocks/space.generator.ts +++ b/demo/api/src/db/fixtures/generators/blocks/space.generator.ts @@ -1,4 +1,4 @@ import { ExtractBlockInputFactoryProps } from "@comet/blocks-api"; import { SpaceBlock, Spacing } from "@src/common/blocks/space.block"; -export const generateSpaceBlock = (): ExtractBlockInputFactoryProps => ({ spacing: Spacing.d200 }); +export const generateSpaceBlock = (): ExtractBlockInputFactoryProps => ({ spacing: Spacing.D200 }); diff --git a/demo/api/src/db/fixtures/generators/blocks/text-image.generator.ts b/demo/api/src/db/fixtures/generators/blocks/text-image.generator.ts index 11d0c9d702..7cb07a69ec 100644 --- a/demo/api/src/db/fixtures/generators/blocks/text-image.generator.ts +++ b/demo/api/src/db/fixtures/generators/blocks/text-image.generator.ts @@ -1,7 +1,7 @@ import { ExtractBlockInputFactoryProps } from "@comet/blocks-api"; import { FileInterface, ImagePosition } from "@comet/cms-api"; import { Config } from "@src/config/config"; -import { TextImageBlock } from "@src/pages/blocks/TextImageBlock"; +import { TextImageBlock } from "@src/documents/pages/blocks/TextImageBlock"; import faker from "faker"; import { generateImageBlock } from "./image.generator"; diff --git a/demo/api/src/db/fixtures/generators/links.generator.ts b/demo/api/src/db/fixtures/generators/links.generator.ts index b5322270e0..ea7beec6e7 100644 --- a/demo/api/src/db/fixtures/generators/links.generator.ts +++ b/demo/api/src/db/fixtures/generators/links.generator.ts @@ -1,8 +1,8 @@ import { InternalLinkBlock } from "@comet/cms-api"; import { EntityRepository } from "@mikro-orm/postgresql"; -import { LinkBlock } from "@src/common/blocks/linkBlock/link.block"; +import { LinkBlock } from "@src/common/blocks/link.block"; import { PageTreeNodesFixtures } from "@src/db/fixtures/fixtures.console"; -import { Link } from "@src/links/entities/link.entity"; +import { Link } from "@src/documents/links/entities/link.entity"; export const generateLinks = async (linksRepository: EntityRepository, pageTreeNodes: PageTreeNodesFixtures): Promise => { await linksRepository.getEntityManager().persistAndFlush( diff --git a/demo/api/src/db/fixtures/generators/many-images-test-page-fixture.service.ts b/demo/api/src/db/fixtures/generators/many-images-test-page-fixture.service.ts index b70d2bd231..d7de15e2ec 100644 --- a/demo/api/src/db/fixtures/generators/many-images-test-page-fixture.service.ts +++ b/demo/api/src/db/fixtures/generators/many-images-test-page-fixture.service.ts @@ -3,11 +3,11 @@ import { InjectRepository } from "@mikro-orm/nestjs"; import { EntityManager, EntityRepository } from "@mikro-orm/postgresql"; import { Injectable } from "@nestjs/common"; import { DamScope } from "@src/dam/dto/dam-scope"; +import { PageContentBlock } from "@src/documents/pages/blocks/page-content.block"; +import { PageInput } from "@src/documents/pages/dto/page.input"; +import { Page } from "@src/documents/pages/entities/page.entity"; import { PageTreeNodeScope } from "@src/page-tree/dto/page-tree-node-scope"; import { PageTreeNodeCategory } from "@src/page-tree/page-tree-node-category"; -import { PageContentBlock } from "@src/pages/blocks/page-content.block"; -import { PageInput } from "@src/pages/dto/page.input"; -import { Page } from "@src/pages/entities/page.entity"; import { UserGroup } from "@src/user-groups/user-group"; import faker from "faker"; @@ -82,6 +82,7 @@ export class ManyImagesTestPageFixtureService { seo: pageInput.seo.transformToBlockData(), createdAt: new Date(), updatedAt: new Date(), + stage: pageInput.stage.transformToBlockData(), }), ); } diff --git a/demo/api/src/db/migrations/Migration20241209092824.ts b/demo/api/src/db/migrations/Migration20241209092824.ts new file mode 100644 index 0000000000..a357a86b5a --- /dev/null +++ b/demo/api/src/db/migrations/Migration20241209092824.ts @@ -0,0 +1,8 @@ +import { Migration } from "@mikro-orm/migrations"; + +export class Migration20241209092824 extends Migration { + async up(): Promise { + this.addSql('truncate table "Page";'); + this.addSql('alter table "Page" add column "stage" json not null;'); + } +} diff --git a/demo/api/src/links/dto/link.input.ts b/demo/api/src/documents/links/dto/link.input.ts similarity index 87% rename from demo/api/src/links/dto/link.input.ts rename to demo/api/src/documents/links/dto/link.input.ts index e91a9d80c1..decc0788f7 100644 --- a/demo/api/src/links/dto/link.input.ts +++ b/demo/api/src/documents/links/dto/link.input.ts @@ -1,7 +1,7 @@ import { BlockInputInterface } from "@comet/blocks-api"; import { RootBlockInputScalar } from "@comet/cms-api"; import { Field, InputType } from "@nestjs/graphql"; -import { LinkBlock } from "@src/common/blocks/linkBlock/link.block"; +import { LinkBlock } from "@src/common/blocks/link.block"; import { Transform } from "class-transformer"; import { ValidateNested } from "class-validator"; diff --git a/demo/api/src/links/entities/link.entity.ts b/demo/api/src/documents/links/entities/link.entity.ts similarity index 94% rename from demo/api/src/links/entities/link.entity.ts rename to demo/api/src/documents/links/entities/link.entity.ts index 224ba738df..c6e4e8a302 100644 --- a/demo/api/src/links/entities/link.entity.ts +++ b/demo/api/src/documents/links/entities/link.entity.ts @@ -10,7 +10,7 @@ import { } from "@comet/cms-api"; import { BaseEntity, Entity, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core"; import { Field, ID, ObjectType } from "@nestjs/graphql"; -import { LinkBlock } from "@src/common/blocks/linkBlock/link.block"; +import { LinkBlock } from "@src/common/blocks/link.block"; import { v4 as uuid } from "uuid"; @EntityInfo(PageTreeNodeDocumentEntityInfoService) diff --git a/demo/api/src/links/links.module.ts b/demo/api/src/documents/links/links.module.ts similarity index 86% rename from demo/api/src/links/links.module.ts rename to demo/api/src/documents/links/links.module.ts index e7923e65fd..a59c75527b 100644 --- a/demo/api/src/links/links.module.ts +++ b/demo/api/src/documents/links/links.module.ts @@ -1,7 +1,7 @@ import { BlocksModule } from "@comet/cms-api"; import { MikroOrmModule } from "@mikro-orm/nestjs"; import { Module } from "@nestjs/common"; -import { PagesModule } from "@src/pages/pages.module"; +import { PagesModule } from "@src/documents/pages/pages.module"; import { Link } from "./entities/link.entity"; import { LinksResolver } from "./links.resolver"; diff --git a/demo/api/src/links/links.resolver.ts b/demo/api/src/documents/links/links.resolver.ts similarity index 100% rename from demo/api/src/links/links.resolver.ts rename to demo/api/src/documents/links/links.resolver.ts diff --git a/demo/api/src/pages/blocks/TextImageBlock.ts b/demo/api/src/documents/pages/blocks/TextImageBlock.ts similarity index 100% rename from demo/api/src/pages/blocks/TextImageBlock.ts rename to demo/api/src/documents/pages/blocks/TextImageBlock.ts diff --git a/demo/api/src/documents/pages/blocks/basic-stage.block.ts b/demo/api/src/documents/pages/blocks/basic-stage.block.ts new file mode 100644 index 0000000000..2c2018630b --- /dev/null +++ b/demo/api/src/documents/pages/blocks/basic-stage.block.ts @@ -0,0 +1,71 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { CallToActionListBlock } from "@src/common/blocks/call-to-action-list.block"; +import { HeadingBlock } from "@src/common/blocks/heading.block"; +import { MediaBlock } from "@src/common/blocks/media.block"; +import { RichTextBlock } from "@src/common/blocks/rich-text.block"; +import { IsEnum, IsInt, Max, Min } from "class-validator"; + +enum Alignment { + left = "left", + center = "center", +} + +class BasicStageBlockData extends BlockData { + @ChildBlock(MediaBlock) + media: BlockDataInterface; + + @ChildBlock(HeadingBlock) + heading: BlockDataInterface; + + @ChildBlock(RichTextBlock) + text: BlockDataInterface; + + @BlockField() + overlay: number; + + @BlockField({ type: "enum", enum: Alignment }) + alignment: Alignment; + + @ChildBlock(CallToActionListBlock) + callToActionList: BlockDataInterface; +} + +class BasicStageBlockInput extends BlockInput { + @ChildBlockInput(MediaBlock) + media: ExtractBlockInput; + + @ChildBlockInput(HeadingBlock) + heading: ExtractBlockInput; + + @ChildBlockInput(RichTextBlock) + text: ExtractBlockInput; + + @BlockField() + @IsInt() + @Min(0) + @Max(90) + overlay: number; + + @IsEnum(Alignment) + @BlockField({ type: "enum", enum: Alignment }) + alignment: Alignment; + + @ChildBlockInput(CallToActionListBlock) + callToActionList: ExtractBlockInput; + + transformToBlockData(): BasicStageBlockData { + return inputToData(BasicStageBlockData, this); + } +} + +export const BasicStageBlock = createBlock(BasicStageBlockData, BasicStageBlockInput, "BasicStage"); diff --git a/demo/api/src/documents/pages/blocks/billboard-teaser.block.ts b/demo/api/src/documents/pages/blocks/billboard-teaser.block.ts new file mode 100644 index 0000000000..a207da88c8 --- /dev/null +++ b/demo/api/src/documents/pages/blocks/billboard-teaser.block.ts @@ -0,0 +1,59 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { CallToActionListBlock } from "@src/common/blocks/call-to-action-list.block"; +import { HeadingBlock } from "@src/common/blocks/heading.block"; +import { MediaBlock } from "@src/common/blocks/media.block"; +import { RichTextBlock } from "@src/common/blocks/rich-text.block"; +import { IsInt, Max, Min } from "class-validator"; + +class BillboardTeaserBlockData extends BlockData { + @ChildBlock(MediaBlock) + media: BlockDataInterface; + + @ChildBlock(HeadingBlock) + heading: BlockDataInterface; + + @ChildBlock(RichTextBlock) + text: BlockDataInterface; + + @BlockField() + overlay: number; + + @ChildBlock(CallToActionListBlock) + callToActionList: BlockDataInterface; +} + +class BillboardTeaserBlockInput extends BlockInput { + @ChildBlockInput(MediaBlock) + media: ExtractBlockInput; + + @ChildBlockInput(HeadingBlock) + heading: ExtractBlockInput; + + @ChildBlockInput(RichTextBlock) + text: ExtractBlockInput; + + @BlockField() + @IsInt() + @Min(0) + @Max(90) + overlay: number; + + @ChildBlockInput(CallToActionListBlock) + callToActionList: ExtractBlockInput; + + transformToBlockData(): BillboardTeaserBlockData { + return inputToData(BillboardTeaserBlockData, this); + } +} + +export const BillboardTeaserBlock = createBlock(BillboardTeaserBlockData, BillboardTeaserBlockInput, "BillboardTeaser"); diff --git a/demo/api/src/documents/pages/blocks/columns.block.ts b/demo/api/src/documents/pages/blocks/columns.block.ts new file mode 100644 index 0000000000..11b1dd5508 --- /dev/null +++ b/demo/api/src/documents/pages/blocks/columns.block.ts @@ -0,0 +1,35 @@ +import { ColumnsBlockFactory, createBlocksBlock } from "@comet/blocks-api"; +import { AnchorBlock } from "@comet/cms-api"; +import { AccordionBlock } from "@src/common/blocks/accordion.block"; +import { MediaGalleryBlock } from "@src/common/blocks/media-gallery.block"; +import { RichTextBlock } from "@src/common/blocks/rich-text.block"; +import { SpaceBlock } from "@src/common/blocks/space.block"; +import { StandaloneCallToActionListBlock } from "@src/common/blocks/standalone-call-to-action-list.block"; +import { StandaloneHeadingBlock } from "@src/common/blocks/standalone-heading.block"; +import { StandaloneMediaBlock } from "@src/common/blocks/standalone-media.block"; + +const ColumnsContentBlock = createBlocksBlock( + { + supportedBlocks: { + accordion: AccordionBlock, + anchor: AnchorBlock, + richtext: RichTextBlock, + space: SpaceBlock, + heading: StandaloneHeadingBlock, + callToActionList: StandaloneCallToActionListBlock, + media: StandaloneMediaBlock, + mediaGallery: MediaGalleryBlock, + }, + }, + { + name: "ColumnsContent", + }, +); + +export const ColumnsBlock = ColumnsBlockFactory.create( + { + layouts: [{ name: "2-20-2" }, { name: "4-16-4" }, { name: "9-6-9" }, { name: "9-9" }, { name: "12-6" }, { name: "6-12" }], + contentBlock: ColumnsContentBlock, + }, + "Columns", +); diff --git a/demo/api/src/documents/pages/blocks/content-group.block.ts b/demo/api/src/documents/pages/blocks/content-group.block.ts new file mode 100644 index 0000000000..073167fd35 --- /dev/null +++ b/demo/api/src/documents/pages/blocks/content-group.block.ts @@ -0,0 +1,73 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + createBlocksBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { AnchorBlock } from "@comet/cms-api"; +import { AccordionBlock } from "@src/common/blocks/accordion.block"; +import { MediaGalleryBlock } from "@src/common/blocks/media-gallery.block"; +import { RichTextBlock } from "@src/common/blocks/rich-text.block"; +import { SpaceBlock } from "@src/common/blocks/space.block"; +import { StandaloneCallToActionListBlock } from "@src/common/blocks/standalone-call-to-action-list.block"; +import { StandaloneHeadingBlock } from "@src/common/blocks/standalone-heading.block"; +import { StandaloneMediaBlock } from "@src/common/blocks/standalone-media.block"; +import { IsEnum } from "class-validator"; + +import { ColumnsBlock } from "./columns.block"; +import { KeyFactsBlock } from "./key-facts.block"; +import { TeaserBlock } from "./teaser.block"; + +const ContentBlock = createBlocksBlock( + { + supportedBlocks: { + accordion: AccordionBlock, + anchor: AnchorBlock, + space: SpaceBlock, + teaser: TeaserBlock, + richText: RichTextBlock, + heading: StandaloneHeadingBlock, + columns: ColumnsBlock, + callToActionList: StandaloneCallToActionListBlock, + keyFacts: KeyFactsBlock, + media: StandaloneMediaBlock, + mediaGallery: MediaGalleryBlock, + }, + }, + { name: "ContentGroupContent" }, +); + +enum BackgroundColor { + default = "default", + lightGray = "lightGray", + darkGray = "darkGray", +} + +class ContentGroupBlockData extends BlockData { + @ChildBlock(ContentBlock) + content: BlockDataInterface; + + @BlockField({ type: "enum", enum: BackgroundColor }) + backgroundColor: BackgroundColor; +} + +class ContentGroupBlockInput extends BlockInput { + @ChildBlockInput(ContentBlock) + content: ExtractBlockInput; + + @IsEnum(BackgroundColor) + @BlockField({ type: "enum", enum: BackgroundColor }) + backgroundColor: BackgroundColor; + + transformToBlockData(): ContentGroupBlockData { + return inputToData(ContentGroupBlockData, this); + } +} + +export const ContentGroupBlock = createBlock(ContentGroupBlockData, ContentGroupBlockInput, "ContentGroup"); diff --git a/demo/api/src/pages/blocks/full-width-image.block.ts b/demo/api/src/documents/pages/blocks/full-width-image.block.ts similarity index 100% rename from demo/api/src/pages/blocks/full-width-image.block.ts rename to demo/api/src/documents/pages/blocks/full-width-image.block.ts diff --git a/demo/api/src/pages/blocks/image-link.block.ts b/demo/api/src/documents/pages/blocks/image-link.block.ts similarity index 70% rename from demo/api/src/pages/blocks/image-link.block.ts rename to demo/api/src/documents/pages/blocks/image-link.block.ts index ebb1617fbc..95c46379d2 100644 --- a/demo/api/src/pages/blocks/image-link.block.ts +++ b/demo/api/src/documents/pages/blocks/image-link.block.ts @@ -1,4 +1,4 @@ import { createImageLinkBlock, DamImageBlock } from "@comet/cms-api"; -import { LinkBlock } from "@src/common/blocks/linkBlock/link.block"; +import { LinkBlock } from "@src/common/blocks/link.block"; export const ImageLinkBlock = createImageLinkBlock({ link: LinkBlock, image: DamImageBlock }); diff --git a/demo/api/src/documents/pages/blocks/key-facts-item.block.ts b/demo/api/src/documents/pages/blocks/key-facts-item.block.ts new file mode 100644 index 0000000000..e81efd8f81 --- /dev/null +++ b/demo/api/src/documents/pages/blocks/key-facts-item.block.ts @@ -0,0 +1,50 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { SvgImageBlock } from "@comet/cms-api"; +import { RichTextBlock } from "@src/common/blocks/rich-text.block"; +import { IsString } from "class-validator"; + +class KeyFactsItemBlockData extends BlockData { + @ChildBlock(SvgImageBlock) + icon: BlockDataInterface; + + @BlockField() + fact: string; + + @BlockField() + label: string; + + @ChildBlock(RichTextBlock) + description: BlockDataInterface; +} + +class KeyFactsItemBlockInput extends BlockInput { + @ChildBlockInput(SvgImageBlock) + icon: ExtractBlockInput; + + @BlockField() + @IsString() + fact: string; + + @BlockField() + @IsString() + label: string; + + @ChildBlockInput(RichTextBlock) + description: ExtractBlockInput; + + transformToBlockData(): KeyFactsItemBlockData { + return inputToData(KeyFactsItemBlockData, this); + } +} + +export const KeyFactsItemBlock = createBlock(KeyFactsItemBlockData, KeyFactsItemBlockInput, "KeyFactsItem"); diff --git a/demo/api/src/documents/pages/blocks/key-facts.block.ts b/demo/api/src/documents/pages/blocks/key-facts.block.ts new file mode 100644 index 0000000000..8213ae8f18 --- /dev/null +++ b/demo/api/src/documents/pages/blocks/key-facts.block.ts @@ -0,0 +1,5 @@ +import { createListBlock } from "@comet/blocks-api"; + +import { KeyFactsItemBlock } from "./key-facts-item.block"; + +export const KeyFactsBlock = createListBlock({ block: KeyFactsItemBlock }, "KeyFacts"); diff --git a/demo/api/src/pages/blocks/layout.block.ts b/demo/api/src/documents/pages/blocks/layout.block.ts similarity index 96% rename from demo/api/src/pages/blocks/layout.block.ts rename to demo/api/src/documents/pages/blocks/layout.block.ts index c38fc8b5f9..17dbbc850b 100644 --- a/demo/api/src/pages/blocks/layout.block.ts +++ b/demo/api/src/documents/pages/blocks/layout.block.ts @@ -9,8 +9,8 @@ import { ExtractBlockInput, inputToData, } from "@comet/blocks-api"; +import { MediaBlock } from "@src/common/blocks/media.block"; import { RichTextBlock } from "@src/common/blocks/rich-text.block"; -import { MediaBlock } from "@src/pages/blocks/media.block"; import { IsEnum } from "class-validator"; enum LayoutBlockLayout { diff --git a/demo/api/src/pages/blocks/page-content.block.ts b/demo/api/src/documents/pages/blocks/page-content.block.ts similarity index 80% rename from demo/api/src/pages/blocks/page-content.block.ts rename to demo/api/src/documents/pages/blocks/page-content.block.ts index ed1251dfd5..e95f7f4bd0 100644 --- a/demo/api/src/pages/blocks/page-content.block.ts +++ b/demo/api/src/documents/pages/blocks/page-content.block.ts @@ -1,40 +1,42 @@ import { BaseBlocksBlockItemData, BaseBlocksBlockItemInput, BlockField, createBlocksBlock } from "@comet/blocks-api"; import { AnchorBlock, DamImageBlock } from "@comet/cms-api"; +import { HeadingBlock } from "@src/common/blocks/heading.block"; import { LinkListBlock } from "@src/common/blocks/link-list.block"; +import { MediaBlock } from "@src/common/blocks/media.block"; import { RichTextBlock } from "@src/common/blocks/rich-text.block"; import { SpaceBlock } from "@src/common/blocks/space.block"; import { NewsDetailBlock } from "@src/news/blocks/news-detail.block"; import { NewsListBlock } from "@src/news/blocks/news-list.block"; -import { LayoutBlock } from "@src/pages/blocks/layout.block"; import { UserGroup } from "@src/user-groups/user-group"; import { IsEnum } from "class-validator"; +import { BillboardTeaserBlock } from "./billboard-teaser.block"; import { ColumnsBlock } from "./columns.block"; +import { ContentGroupBlock } from "./content-group.block"; import { FullWidthImageBlock } from "./full-width-image.block"; -import { HeadlineBlock } from "./headline.block"; import { ImageLinkBlock } from "./image-link.block"; -import { MediaBlock } from "./media.block"; +import { LayoutBlock } from "./layout.block"; import { TeaserBlock } from "./teaser.block"; import { TextImageBlock } from "./TextImageBlock"; -import { TwoListsBlock } from "./two-lists.block"; const supportedBlocks = { - space: SpaceBlock, - richtext: RichTextBlock, - headline: HeadlineBlock, + anchor: AnchorBlock, + billboardTeaser: BillboardTeaserBlock, + columns: ColumnsBlock, + contentGroup: ContentGroupBlock, + fullWidthImage: FullWidthImageBlock, + heading: HeadingBlock, image: DamImageBlock, - textImage: TextImageBlock, + imageLink: ImageLinkBlock, + layout: LayoutBlock, linkList: LinkListBlock, - fullWidthImage: FullWidthImageBlock, - columns: ColumnsBlock, - anchor: AnchorBlock, - twoLists: TwoListsBlock, media: MediaBlock, - teaser: TeaserBlock, newsDetail: NewsDetailBlock, - imageLink: ImageLinkBlock, newsList: NewsListBlock, - layout: LayoutBlock, + richText: RichTextBlock, + space: SpaceBlock, + teaser: TeaserBlock, + textImage: TextImageBlock, }; class BlocksBlockItemData extends BaseBlocksBlockItemData(supportedBlocks) { diff --git a/demo/api/src/pages/blocks/seo.block.ts b/demo/api/src/documents/pages/blocks/seo.block.ts similarity index 100% rename from demo/api/src/pages/blocks/seo.block.ts rename to demo/api/src/documents/pages/blocks/seo.block.ts diff --git a/demo/api/src/documents/pages/blocks/stage.block.ts b/demo/api/src/documents/pages/blocks/stage.block.ts new file mode 100644 index 0000000000..81c9a3a2ee --- /dev/null +++ b/demo/api/src/documents/pages/blocks/stage.block.ts @@ -0,0 +1,6 @@ +import { createListBlock } from "@comet/blocks-api"; + +import { BasicStageBlock } from "./basic-stage.block"; + +/* If you need multiple stage blocks, you should use createBlocksBlock instead of createListBlock */ +export const StageBlock = createListBlock({ block: BasicStageBlock }, "Stage"); diff --git a/demo/api/src/documents/pages/blocks/teaser-item.block.ts b/demo/api/src/documents/pages/blocks/teaser-item.block.ts new file mode 100644 index 0000000000..c922fa789c --- /dev/null +++ b/demo/api/src/documents/pages/blocks/teaser-item.block.ts @@ -0,0 +1,50 @@ +import { + BlockData, + BlockDataInterface, + BlockField, + BlockInput, + ChildBlock, + ChildBlockInput, + createBlock, + ExtractBlockInput, + inputToData, +} from "@comet/blocks-api"; +import { MediaBlock } from "@src/common/blocks/media.block"; +import { RichTextBlock } from "@src/common/blocks/rich-text.block"; +import { TextLinkBlock } from "@src/common/blocks/text-link.block"; +import { IsString } from "class-validator"; + +class TeaserItemBlockData extends BlockData { + @ChildBlock(MediaBlock) + media: BlockDataInterface; + + @BlockField() + title: string; + + @ChildBlock(RichTextBlock) + description: BlockDataInterface; + + @ChildBlock(TextLinkBlock) + link: BlockDataInterface; +} + +class TeaserItemBlockInput extends BlockInput { + @ChildBlockInput(MediaBlock) + media: ExtractBlockInput; + + @BlockField() + @IsString() + title: string; + + @ChildBlockInput(RichTextBlock) + description: ExtractBlockInput; + + @ChildBlockInput(TextLinkBlock) + link: ExtractBlockInput; + + transformToBlockData(): TeaserItemBlockData { + return inputToData(TeaserItemBlockData, this); + } +} + +export const TeaserItemBlock = createBlock(TeaserItemBlockData, TeaserItemBlockInput, "TeaserItem"); diff --git a/demo/api/src/documents/pages/blocks/teaser.block.ts b/demo/api/src/documents/pages/blocks/teaser.block.ts new file mode 100644 index 0000000000..1679cd6053 --- /dev/null +++ b/demo/api/src/documents/pages/blocks/teaser.block.ts @@ -0,0 +1,5 @@ +import { createListBlock } from "@comet/blocks-api"; + +import { TeaserItemBlock } from "./teaser-item.block"; + +export const TeaserBlock = createListBlock({ block: TeaserItemBlock }, "Teaser"); diff --git a/demo/api/src/pages/dto/page.input.ts b/demo/api/src/documents/pages/dto/page.input.ts similarity index 76% rename from demo/api/src/pages/dto/page.input.ts rename to demo/api/src/documents/pages/dto/page.input.ts index b6dc06fd77..64ef88e455 100644 --- a/demo/api/src/pages/dto/page.input.ts +++ b/demo/api/src/documents/pages/dto/page.input.ts @@ -6,6 +6,7 @@ import { ValidateNested } from "class-validator"; import { PageContentBlock } from "../blocks/page-content.block"; import { SeoBlock } from "../blocks/seo.block"; +import { StageBlock } from "../blocks/stage.block"; @InputType() export class PageInput { @@ -18,4 +19,9 @@ export class PageInput { @Transform(({ value }) => SeoBlock.blockInputFactory(value), { toClassOnly: true }) @ValidateNested() seo: BlockInputInterface; + + @Field(() => RootBlockInputScalar(StageBlock)) + @Transform(({ value }) => StageBlock.blockInputFactory(value), { toClassOnly: true }) + @ValidateNested() + stage: BlockInputInterface; } diff --git a/demo/api/src/pages/entities/page.entity.ts b/demo/api/src/documents/pages/entities/page.entity.ts similarity index 88% rename from demo/api/src/pages/entities/page.entity.ts rename to demo/api/src/documents/pages/entities/page.entity.ts index 3984fe945e..b2f70d4f5c 100644 --- a/demo/api/src/pages/entities/page.entity.ts +++ b/demo/api/src/documents/pages/entities/page.entity.ts @@ -14,6 +14,7 @@ import { v4 as uuid } from "uuid"; import { PageContentBlock } from "../blocks/page-content.block"; import { SeoBlock } from "../blocks/seo.block"; +import { StageBlock } from "../blocks/stage.block"; @EntityInfo(PageTreeNodeDocumentEntityInfoService) @Entity() @@ -39,6 +40,11 @@ export class Page extends BaseEntity implements DocumentInterface { @Field(() => RootBlockDataScalar(SeoBlock)) seo: BlockDataInterface; + @RootBlock(StageBlock) + @Property({ customType: new RootBlockType(StageBlock) }) + @Field(() => RootBlockDataScalar(StageBlock)) + stage: BlockDataInterface; + @Property({ columnType: "timestamp with time zone", }) diff --git a/demo/api/src/pages/pages.module.ts b/demo/api/src/documents/pages/pages.module.ts similarity index 100% rename from demo/api/src/pages/pages.module.ts rename to demo/api/src/documents/pages/pages.module.ts diff --git a/demo/api/src/pages/pages.resolver.ts b/demo/api/src/documents/pages/pages.resolver.ts similarity index 98% rename from demo/api/src/pages/pages.resolver.ts rename to demo/api/src/documents/pages/pages.resolver.ts index 66d1a4a142..b137ec3b89 100644 --- a/demo/api/src/pages/pages.resolver.ts +++ b/demo/api/src/documents/pages/pages.resolver.ts @@ -68,6 +68,7 @@ export class PagesResolver { id: pageId, content: input.content.transformToBlockData(), seo: input.seo.transformToBlockData(), + stage: input.stage.transformToBlockData(), }); this.entityManager.persist(page); diff --git a/demo/api/src/footer/blocks/footer-content.block.ts b/demo/api/src/footer/blocks/footer-content.block.ts index b45cbb7eef..9921c32b54 100644 --- a/demo/api/src/footer/blocks/footer-content.block.ts +++ b/demo/api/src/footer/blocks/footer-content.block.ts @@ -6,65 +6,47 @@ import { ChildBlock, ChildBlockInput, createBlock, - createListBlock, ExtractBlockInput, inputToData, } from "@comet/blocks-api"; +import { DamImageBlock } from "@comet/cms-api"; import { LinkListBlock } from "@src/common/blocks/link-list.block"; -import { IsOptional, IsString } from "class-validator"; +import { RichTextBlock } from "@src/common/blocks/rich-text.block"; +import { IsString } from "class-validator"; -import { FooterLinkSectionBlock } from "./footer-link-section.block"; +class FooterContentBlockData extends BlockData { + @ChildBlock(RichTextBlock) + text: ExtractBlockInput; -export const FooterTopLinksBlock = createListBlock({ block: FooterLinkSectionBlock }, "FooterTopLinks"); + @ChildBlock(DamImageBlock) + image: BlockDataInterface; -class FooterBlockData extends BlockData { @ChildBlock(LinkListBlock) - popularTopicsLinks: BlockDataInterface; - - @ChildBlock(LinkListBlock) - aboutLinks: BlockDataInterface; - - @ChildBlock(LinkListBlock) - bottomLinks: BlockDataInterface; - - @BlockField() - copyrightNotice?: string; - - @BlockField() - location?: string; + linkList: BlockDataInterface; @BlockField() - contactUs?: string; + copyrightNotice: string; } -class FooterBlockInput extends BlockInput { - @ChildBlockInput(LinkListBlock) - popularTopicsLinks: ExtractBlockInput; +class FooterContentBlockInput extends BlockInput { + @ChildBlockInput(RichTextBlock) + text: ExtractBlockInput; - @ChildBlockInput(LinkListBlock) - aboutLinks: ExtractBlockInput; + @ChildBlockInput(DamImageBlock) + image: ExtractBlockInput; @ChildBlockInput(LinkListBlock) - bottomLinks: ExtractBlockInput; - - @BlockField() - @IsOptional() - @IsString() - copyrightNotice?: string; - - @BlockField() - @IsOptional() - @IsString() - location?: string; + linkList: ExtractBlockInput; @BlockField() - @IsOptional() @IsString() - contactUs?: string; + copyrightNotice: string; - transformToBlockData(): FooterBlockData { - return inputToData(FooterBlockData, this); + transformToBlockData(): FooterContentBlockData { + return inputToData(FooterContentBlockData, this); } } -export const FooterContentBlock = createBlock(FooterBlockData, FooterBlockInput, "FooterContent"); +export const FooterContentBlock = createBlock(FooterContentBlockData, FooterContentBlockInput, { + name: "FooterContent", +}); diff --git a/demo/api/src/footer/entities/footer-content-scope.entity.ts b/demo/api/src/footer/dto/footer-scope.ts similarity index 83% rename from demo/api/src/footer/entities/footer-content-scope.entity.ts rename to demo/api/src/footer/dto/footer-scope.ts index 5eaf1d17a9..945988e5fa 100644 --- a/demo/api/src/footer/entities/footer-content-scope.entity.ts +++ b/demo/api/src/footer/dto/footer-scope.ts @@ -4,8 +4,8 @@ import { IsString } from "class-validator"; @Embeddable() @ObjectType() -@InputType("FooterContentScopeInput") -export class FooterContentScope { +@InputType("FooterScopeInput") +export class FooterScope { @Property({ columnType: "text" }) @Field() @IsString() diff --git a/demo/api/src/footer/entities/footer.entity.ts b/demo/api/src/footer/entities/footer.entity.ts index c98fbaf730..0288b58c85 100644 --- a/demo/api/src/footer/entities/footer.entity.ts +++ b/demo/api/src/footer/entities/footer.entity.ts @@ -1,30 +1,31 @@ -import { BlockDataInterface, RootBlock, RootBlockEntity } from "@comet/blocks-api"; -import { CrudSingleGenerator, RootBlockDataScalar, RootBlockType } from "@comet/cms-api"; +import { ExtractBlockData, RootBlock, RootBlockEntity } from "@comet/blocks-api"; +import { CrudSingleGenerator, DocumentInterface, RootBlockDataScalar, RootBlockType } from "@comet/cms-api"; import { BaseEntity, Embedded, Entity, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core"; import { Field, ID, ObjectType } from "@nestjs/graphql"; -import { v4 as uuid } from "uuid"; +import { v4 } from "uuid"; import { FooterContentBlock } from "../blocks/footer-content.block"; -import { FooterContentScope } from "./footer-content-scope.entity"; +import { FooterScope } from "../dto/footer-scope"; @Entity() @ObjectType() @RootBlockEntity() @CrudSingleGenerator({ targetDirectory: `${__dirname}/../generated/`, requiredPermission: ["pageTree"] }) -export class Footer extends BaseEntity { +export class Footer extends BaseEntity implements DocumentInterface { [OptionalProps]?: "createdAt" | "updatedAt"; - @PrimaryKey({ columnType: "uuid" }) + @PrimaryKey({ type: "uuid" }) @Field(() => ID) - id: string = uuid(); + id: string = v4(); + @RootBlock(FooterContentBlock) @Property({ customType: new RootBlockType(FooterContentBlock) }) @Field(() => RootBlockDataScalar(FooterContentBlock)) - content: BlockDataInterface; + content: ExtractBlockData; - @Embedded(() => FooterContentScope) - @Field(() => FooterContentScope) - scope: FooterContentScope; + @Embedded(() => FooterScope) + @Field(() => FooterScope) + scope: FooterScope; @Property({ columnType: "timestamp with time zone" }) @Field() diff --git a/demo/api/src/footer/footer.module.ts b/demo/api/src/footer/footer.module.ts index 29d4c23118..2c58ca1240 100644 --- a/demo/api/src/footer/footer.module.ts +++ b/demo/api/src/footer/footer.module.ts @@ -2,13 +2,13 @@ import { DependenciesResolverFactory } from "@comet/cms-api"; import { MikroOrmModule } from "@mikro-orm/nestjs"; import { Module } from "@nestjs/common"; +import { FooterScope } from "./dto/footer-scope"; import { Footer } from "./entities/footer.entity"; -import { FooterContentScope } from "./entities/footer-content-scope.entity"; import { FooterResolver } from "./generated/footer.resolver"; import { FootersService } from "./generated/footers.service"; @Module({ - imports: [MikroOrmModule.forFeature([Footer, FooterContentScope])], - providers: [FooterResolver, FootersService, DependenciesResolverFactory.create(Footer)], + imports: [MikroOrmModule.forFeature([Footer, FooterScope])], + providers: [FootersService, FooterResolver, DependenciesResolverFactory.create(Footer)], }) export class FooterModule {} diff --git a/demo/api/src/footer/generated/footer.resolver.ts b/demo/api/src/footer/generated/footer.resolver.ts index 83787f2d09..9c854c92c7 100644 --- a/demo/api/src/footer/generated/footer.resolver.ts +++ b/demo/api/src/footer/generated/footer.resolver.ts @@ -5,8 +5,8 @@ import { InjectRepository } from "@mikro-orm/nestjs"; import { EntityManager, EntityRepository } from "@mikro-orm/postgresql"; import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; +import { FooterScope } from "../dto/footer-scope"; import { Footer } from "../entities/footer.entity"; -import { FooterContentScope } from "../entities/footer-content-scope.entity"; import { FooterInput } from "./dto/footer.input"; import { FootersService } from "./footers.service"; @@ -20,7 +20,7 @@ export class FooterResolver { ) {} @Query(() => Footer, { nullable: true }) - async footer(@Args("scope", { type: () => FooterContentScope }) scope: FooterContentScope): Promise