diff --git a/demo/admin/src/App.tsx b/demo/admin/src/App.tsx index 6a4891ddb6..0e13218763 100644 --- a/demo/admin/src/App.tsx +++ b/demo/admin/src/App.tsx @@ -1,13 +1,10 @@ -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, @@ -19,26 +16,24 @@ import { SitesConfigProvider, } 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 { ImportFromUnsplash } from "@src/dam/ImportFromUnsplash"; -import { pageTreeCategories } from "@src/pageTree/pageTreeCategories"; +import { getMessages } from "@src/lang"; import { theme } from "@src/theme"; import { HTML5toTouch } from "rdndmb-html5-to-touch"; -import { Component, Fragment } from "react"; +import { 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 { createApolloClient } from "./common/apollo/createApolloClient"; +import { ContentScopeProvider } from "./common/ContentScopeProvider"; +import { additionalPageTreeNodeFieldsFragment } from "./common/EditPageNode"; import MasterHeader from "./common/MasterHeader"; -import MasterMenu, { masterMenuData, pageTreeDocumentTypes } from "./common/MasterMenu"; -import { getMessages } from "./lang"; -import { Link } from "./links/Link"; +import { AppMasterMenu, masterMenuData, pageTreeCategories, pageTreeDocumentTypes } from "./common/MasterMenu"; +import { ConfigProvider, createConfig } from "./config"; +import { ImportFromUnsplash } from "./dam/ImportFromUnsplash"; +import { Link } from "./documents/links/Link"; +import { Page } from "./documents/pages/Page"; 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, preloginEnabled: siteConfig.preloginEnabled || false, - blockPreviewBaseUrl: - siteConfig.scope.domain === "secondary" - ? `${siteConfig.url}/block-preview` - : `${siteConfig.url}/block-preview/${scope.domain}/${scope.language}`, - sitePreviewApiUrl: `${siteConfig.url}/api/site-preview`, + blockPreviewBaseUrl: `${config.previewUrl}/block-preview/${scope.domain}/${scope.language}`, + sitePreviewApiUrl: `${config.previewUrl}/api/site-preview`, }; }, }} @@ -105,7 +95,7 @@ class App extends Component { }} > - scope.domain}> + scope.domain}> @@ -124,40 +114,39 @@ class App extends Component { pageTreeDocumentTypes={pageTreeDocumentTypes} additionalPageTreeNodeFragment={additionalPageTreeNodeFieldsFragment} > - - - - {({ match }) => ( - - {/* @TODO: add preview to contentScope once site is capable of contentScope */} - ( - { - return `/${scope.language}${path}`; - }} - {...props} - /> - )} - /> - ( - - - - )} - /> - - )} - - + + + + + + {({ match }) => ( + + ( + { + return `/${scope.language}${path}`; + }} + {...props} + /> + )} + /> + ( + + + + )} + /> + + )} + + + @@ -169,9 +158,8 @@ class App extends Component { - - - ); - } + + + + ); } -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 a31cba476e..d1efa79c0e 100644 --- a/demo/admin/src/common/MasterMenu.tsx +++ b/demo/admin/src/common/MasterMenu.tsx @@ -1,104 +1,74 @@ -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 { ImportFromUnsplash } from "@src/dam/ImportFromUnsplash"; -import Dashboard from "@src/dashboard/Dashboard"; -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 { DashboardPage } from "@src/dashboard/DashboardPage"; +import { Link } from "@src/documents/links/Link"; +import { Page } from "@src/documents/pages/Page"; +import { EditFooterPage } from "@src/footer/EditFooterPage"; import { NewsPage } from "@src/news/generated/NewsPage"; -import MainMenu from "@src/pages/mainMenu/MainMenu"; -import { Page } from "@src/pages/Page"; -import { categoryToUrlParam, pageTreeCategories, 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 { FormattedMessage } from "react-intl"; -import { Redirect, RouteComponentProps } from "react-router-dom"; 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"] }); +const RedirectsPage = createRedirectsPage({ scopeParts: ["domain"] }); export const masterMenuData: MasterMenuData = [ { type: "route", primary: , - icon: , + icon: , route: { path: "/dashboard", - component: Dashboard, + component: DashboardPage, }, }, { - type: "collapsible", + type: "route", primary: , icon: , - items: pageTreeCategories.map((category) => ({ - type: "route", - primary: category.label, - to: `/pages/pagetree/${categoryToUrlParam(category.category as GQLPageTreeNodeCategory)}`, - })), route: { - path: "/pages/pagetree/:category", - render: ({ match }: RouteComponentProps<{ category: string }>) => { - const category = urlParamToCategory(match.params.category); - - if (category === undefined) { - return ; - } - - return ( - => { - if (category === "TopMenu") { - return { - Page, - PredefinedPage, - }; - } - - return { - Page, - PredefinedPage, - Link, - }; - }} - editPageNode={EditPageNode} - category={category} - renderContentScopeIndicator={(scope: ContentScope) => } - /> - ); - }, + path: "/pages/pagetree/main-navigation", + render: () => ( + } + /> + ), }, requiredPermission: "pageTree", }, @@ -124,26 +94,37 @@ export const masterMenuData: MasterMenuData = [ icon: , route: { path: "/assets", - render: () => } />, + component: DamPage, }, requiredPermission: "dam", }, { type: "collapsible", - primary: , + primary: , icon: , items: [ { type: "route", - primary: , + primary: , route: { - path: "/project-snips/main-menu", - component: MainMenu, + path: "/project-snips/footer", + component: EditFooterPage, }, + requiredPermission: "pageTree", }, ], requiredPermission: "pageTree", }, + { + type: "route", + primary: , + icon: , + route: { + path: "/user-permissions", + component: UserPermissionsPage, + }, + requiredPermission: "userPermissions", + }, { type: "collapsible", primary: , @@ -309,5 +290,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..476264e950 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"; @@ -22,12 +21,14 @@ export function createConfig() { ...cometConfig, apiUrl: environmentVariables.API_URL, adminUrl: environmentVariables.ADMIN_URL, + previewUrl: environmentVariables.PREVIEW_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 67% rename from demo/admin/src/dashboard/Dashboard.tsx rename to demo/admin/src/dashboard/DashboardPage.tsx index 381197d431..1b489345a5 100644 --- a/demo/admin/src/dashboard/Dashboard.tsx +++ b/demo/admin/src/dashboard/DashboardPage.tsx @@ -1,5 +1,5 @@ -import { MainContent, Stack, Toolbar } from "@comet/admin"; -import { ContentScopeIndicator, DashboardHeader, LatestBuildsDashboardWidget, useUserPermissionCheck } from "@comet/cms-admin"; +import { MainContent, Stack } from "@comet/admin"; +import { DashboardHeader, useUserPermissionCheck } from "@comet/cms-admin"; import { Grid } from "@mui/material"; import { useIntl } from "react-intl"; @@ -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 ( { "2x": backgroundImage2x, }} /> - } /> {isAllowed("pageTree") && } - {import.meta.env.MODE !== "development" && } ); -}; - -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..d595b4a0b2 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": [ @@ -16,6 +189,236 @@ } ] }, + { + "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": [ @@ -49,15 +452,86 @@ }, "nullable": false } - ], - "inputFields": [ + ], + "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", + "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": { + "accordion": "Accordion", + "anchor": "Anchor", + "richtext": "RichText", + "space": "Space", + "heading": "StandaloneHeading", + "callToActionList": "StandaloneCallToActionList", + "media": "StandaloneMedia", + "mediaGallery": "MediaGallery" + }, + "nullable": false + } + ] + }, "nullable": false - }, + } + ], + "inputFields": [ { - "name": "columns", + "name": "blocks", "kind": "NestedObjectList", "object": { "fields": [ @@ -71,10 +545,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 +572,38 @@ ] }, { - "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 +629,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 +673,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 } @@ -202,10 +733,7 @@ { "name": "openFileType", "kind": "Enum", - "enum": [ - "NewTab", - "Download" - ], + "enum": ["NewTab", "Download"], "nullable": false } ], @@ -218,10 +746,7 @@ { "name": "openFileType", "kind": "Enum", - "enum": [ - "NewTab", - "Download" - ], + "enum": ["NewTab", "Download"], "nullable": false } ] @@ -402,76 +927,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 +975,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,136 +996,30 @@ "name": "copyrightNotice", "kind": "String", "nullable": false - }, - { - "name": "location", - "kind": "String", - "nullable": false - }, - { - "name": "contactUs", - "kind": "String", - "nullable": false } ], "inputFields": [ { - "name": "popularTopicsLinks", - "kind": "Block", - "block": "LinkList", - "nullable": false - }, - { - "name": "aboutLinks", - "kind": "Block", - "block": "LinkList", - "nullable": false - }, - { - "name": "bottomLinks", - "kind": "Block", - "block": "LinkList", - "nullable": false - }, - { - "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", + "name": "text", "kind": "Block", - "block": "LinkList", - "nullable": false - } - ], - "inputFields": [ - { - "name": "title", - "kind": "String", + "block": "RichText", "nullable": false }, { - "name": "links", + "name": "image", "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 - } - ] - }, + "block": "DamImage", + "nullable": false + }, + { + "name": "linkList", + "kind": "Block", + "block": "LinkList", + "nullable": false + }, + { + "name": "copyrightNotice", + "kind": "String", "nullable": false } ] @@ -707,12 +1056,13 @@ ] }, { - "name": "Headline", + "name": "Heading", "fields": [ { "name": "eyebrow", - "kind": "String", - "nullable": true + "kind": "Block", + "block": "RichText", + "nullable": false }, { "name": "headline", @@ -721,24 +1071,18 @@ "nullable": false }, { - "name": "level", + "name": "htmlTag", "kind": "Enum", - "enum": [ - "header-one", - "header-two", - "header-three", - "header-four", - "header-five", - "header-six" - ], + "enum": ["H1", "H2", "H3", "H4", "H5", "H6"], "nullable": false } ], "inputFields": [ { "name": "eyebrow", - "kind": "String", - "nullable": true + "kind": "Block", + "block": "RichText", + "nullable": false }, { "name": "headline", @@ -747,16 +1091,9 @@ "nullable": false }, { - "name": "level", + "name": "htmlTag", "kind": "Enum", - "enum": [ - "header-one", - "header-two", - "header-three", - "header-four", - "header-five", - "header-six" - ], + "enum": ["H1", "H2", "H3", "H4", "H5", "H6"], "nullable": false } ] @@ -848,21 +1185,121 @@ } ] }, + { + "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": [ { "name": "layout", "kind": "Enum", - "enum": [ - "layout1", - "layout2", - "layout3", - "layout4", - "layout5", - "layout6", - "layout7" - ], + "enum": ["layout1", "layout2", "layout3", "layout4", "layout5", "layout6", "layout7"], "nullable": false }, { @@ -894,15 +1331,7 @@ { "name": "layout", "kind": "Enum", - "enum": [ - "layout1", - "layout2", - "layout3", - "layout4", - "layout5", - "layout6", - "layout7" - ], + "enum": ["layout1", "layout2", "layout3", "layout4", "layout5", "layout6", "layout7"], "nullable": false }, { @@ -953,12 +1382,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 +1414,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,25 +1462,172 @@ { "name": "props", "kind": "Block", - "block": "DemoTextLink", + "block": "TextLink", + "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 + } + ] + }, + "nullable": false + } + ] + }, + { + "name": "Media", + "fields": [ + { + "name": "attachedBlocks", + "kind": "NestedObjectList", + "object": { + "fields": [ + { + "name": "type", + "kind": "String", "nullable": false }, { - "name": "userGroup", - "kind": "Enum", - "enum": [ - "All", - "Admin", - "User" - ], + "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 } - ], - "inputFields": [ + ] + }, + { + "name": "MediaGalleryList", + "fields": [ { "name": "blocks", "kind": "NestedObjectList", @@ -1070,89 +1646,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 +1709,8 @@ "name": "props", "kind": "OneOfBlocks", "blocks": { - "headline": "Headline", - "richtext": "RichText", + "headline": "Heading", + "richText": "RichText", "image": "DamImage", "textImage": "TextImage" }, @@ -1220,8 +1746,8 @@ "name": "props", "kind": "OneOfBlocks", "blocks": { - "headline": "Headline", - "richtext": "RichText", + "headline": "Heading", + "richText": "RichText", "image": "DamImage", "textImage": "TextImage" }, @@ -1409,33 +1935,29 @@ "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", "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 }, { "name": "userGroup", "kind": "Enum", - "enum": [ - "All", - "Admin", - "User" - ], + "enum": ["All", "Admin", "User"], "nullable": false } ] @@ -1468,33 +1990,29 @@ "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", "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 }, { "name": "userGroup", "kind": "Enum", - "enum": [ - "All", - "Admin", - "User" - ], + "enum": ["All", "Admin", "User"], "nullable": false } ] @@ -1596,14 +2114,7 @@ { "name": "focalPoint", "kind": "Enum", - "enum": [ - "SMART", - "CENTER", - "NORTHWEST", - "NORTHEAST", - "SOUTHWEST", - "SOUTHEAST" - ], + "enum": ["SMART", "CENTER", "NORTHWEST", "NORTHEAST", "SOUTHWEST", "SOUTHEAST"], "nullable": false }, { @@ -1656,14 +2167,7 @@ { "name": "focalPoint", "kind": "Enum", - "enum": [ - "SMART", - "CENTER", - "NORTHWEST", - "NORTHEAST", - "SOUTHWEST", - "SOUTHEAST" - ], + "enum": ["SMART", "CENTER", "NORTHWEST", "NORTHEAST", "SOUTHWEST", "SOUTHEAST"], "nullable": false }, { @@ -1710,14 +2214,7 @@ { "name": "focalPoint", "kind": "Enum", - "enum": [ - "SMART", - "CENTER", - "NORTHWEST", - "NORTHEAST", - "SOUTHWEST", - "SOUTHEAST" - ], + "enum": ["SMART", "CENTER", "NORTHWEST", "NORTHEAST", "SOUTHWEST", "SOUTHEAST"], "nullable": false }, { @@ -1870,33 +2367,87 @@ { "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" - ], + "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 + } + ], + "inputFields": [ + { + "name": "htmlTitle", + "kind": "String", + "nullable": true + }, + { + "name": "metaDescription", + "kind": "String", + "nullable": true + }, + { + "name": "openGraphTitle", + "kind": "String", + "nullable": true + }, + { + "name": "openGraphDescription", + "kind": "String", + "nullable": true + }, + { + "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" - ], + "enum": ["always", "hourly", "daily", "weekly", "monthly", "yearly", "never"], "nullable": false }, { @@ -1905,133 +2456,208 @@ "nullable": true }, { - "name": "alternativeLinks", + "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": "code", + "name": "key", "kind": "String", - "nullable": true + "nullable": false }, { - "name": "url", - "kind": "String", - "nullable": true + "name": "visible", + "kind": "Boolean", + "nullable": false + }, + { + "name": "props", + "kind": "Block", + "block": "BasicStage", + "nullable": false } ] }, "nullable": false } - ], - "inputFields": [ - { - "name": "htmlTitle", - "kind": "String", - "nullable": true - }, - { - "name": "metaDescription", - "kind": "String", - "nullable": true - }, - { - "name": "openGraphTitle", - "kind": "String", - "nullable": true - }, - { - "name": "openGraphDescription", - "kind": "String", - "nullable": true - }, + ] + }, + { + "name": "StandaloneCallToActionList", + "fields": [ { - "name": "openGraphImage", + "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" - ], + "enum": ["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" - ], + "enum": ["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 +2737,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 } ] @@ -2192,10 +2861,7 @@ { "name": "imagePosition", "kind": "Enum", - "enum": [ - "left", - "right" - ], + "enum": ["left", "right"], "nullable": false }, { @@ -2220,10 +2886,7 @@ { "name": "imagePosition", "kind": "Enum", - "enum": [ - "left", - "right" - ], + "enum": ["left", "right"], "nullable": false }, { @@ -2234,89 +2897,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 } ] @@ -2439,4 +3043,4 @@ } ] } -] \ No newline at end of file +] 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..3cb6cca0fd 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,18 +351,17 @@ enum PredefinedPageType { News } -type FooterContentScope { +type FooterScope { domain: String! language: String! } -type Footer { +type Footer implements DocumentInterface { id: ID! + updatedAt: DateTime! content: FooterContentBlockData! - scope: FooterContentScope! + scope: FooterScope! createdAt: DateTime! - updatedAt: DateTime! - dependencies(offset: Int! = 0, limit: Int! = 25, filter: DependencyFilter, forceRefresh: Boolean! = false): PaginatedDependencies! } """FooterContent root block data""" @@ -691,7 +694,7 @@ input DamScopeInput { domain: String! } -input FooterContentScopeInput { +input FooterScopeInput { domain: String! language: String! } @@ -794,7 +797,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 +1221,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 +1274,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 +1283,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 82% 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..6f4fbc7410 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,40 @@ 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 { 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, 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..d33dc4c150 100644 --- a/demo/api/src/footer/entities/footer.entity.ts +++ b/demo/api/src/footer/entities/footer.entity.ts @@ -1,30 +1,33 @@ -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() +@ObjectType({ + implements: () => [DocumentInterface], +}) @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..4eb2c7b64f 100644 --- a/demo/api/src/footer/footer.module.ts +++ b/demo/api/src/footer/footer.module.ts @@ -1,14 +1,12 @@ -import { DependenciesResolverFactory } from "@comet/cms-api"; import { MikroOrmModule } from "@mikro-orm/nestjs"; import { Module } from "@nestjs/common"; 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])], + providers: [FootersService, FooterResolver], }) 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