diff --git a/core-web/libs/sdk/angular/package.json b/core-web/libs/sdk/angular/package.json index d9d01254e7de..ae7c15e917f2 100644 --- a/core-web/libs/sdk/angular/package.json +++ b/core-web/libs/sdk/angular/package.json @@ -1,32 +1,32 @@ { - "name": "@dotcms/angular", - "version": "0.0.1-alpha.33", - "peerDependencies": { - "@angular/common": "^17.1.0", - "@angular/core": "^17.1.0", - "@angular/router": "^17.1.0", - "@dotcms/client": "0.0.1-alpha.33", - "@tinymce/tinymce-angular": "^8.0.0", - "rxjs": "^7.8.0" - }, - "description": "Official Angular Components library to render a dotCMS page.", - "repository": { - "type": "git", - "url": "git+https://github.com/dotCMS/core.git#master" - }, - "keywords": [ - "dotCMS", - "CMS", - "Content Management", - "API Client", - "REST API", - "Angular", - "Components" - ], - "author": "dotcms ", - "license": "MIT", - "bugs": { - "url": "https://github.com/dotCMS/core/issues" - }, - "homepage": "https://github.com/dotCMS/core/tree/master/core-web/libs/sdk/angular/README.md" + "name": "@dotcms/angular", + "version": "0.0.1-alpha.35", + "peerDependencies": { + "@angular/common": "^17.1.0", + "@angular/core": "^17.1.0", + "@angular/router": "^17.1.0", + "@dotcms/client": "0.0.1-alpha.35", + "@tinymce/tinymce-angular": "^8.0.0", + "rxjs": "^7.8.0" + }, + "description": "Official Angular Components library to render a dotCMS page.", + "repository": { + "type": "git", + "url": "git+https://github.com/dotCMS/core.git#master" + }, + "keywords": [ + "dotCMS", + "CMS", + "Content Management", + "API Client", + "REST API", + "Angular", + "Components" + ], + "author": "dotcms ", + "license": "MIT", + "bugs": { + "url": "https://github.com/dotCMS/core/issues" + }, + "homepage": "https://github.com/dotCMS/core/tree/master/core-web/libs/sdk/angular/README.md" } diff --git a/core-web/libs/sdk/client/package.json b/core-web/libs/sdk/client/package.json index b3c43998a5f5..590e13638f36 100644 --- a/core-web/libs/sdk/client/package.json +++ b/core-web/libs/sdk/client/package.json @@ -1,25 +1,25 @@ { - "name": "@dotcms/client", - "version": "0.0.1-alpha.33", - "description": "Official JavaScript library for interacting with DotCMS REST APIs.", - "repository": { - "type": "git", - "url": "git+https://github.com/dotCMS/core.git#master" - }, - "scripts": { - "build": "nx run sdk-client:build:js; cd ../../../../dotCMS/src/main/webapp/html/js/editor-js; rm -rf src package.json *.esm.d.ts" - }, - "keywords": [ - "dotCMS", - "CMS", - "Content Management", - "API Client", - "REST API" - ], - "author": "dotcms ", - "license": "MIT", - "bugs": { - "url": "https://github.com/dotCMS/core/issues" - }, - "homepage": "https://github.com/dotCMS/core/tree/master/core-web/libs/sdk/client/README.md" + "name": "@dotcms/client", + "version": "0.0.1-alpha.35", + "description": "Official JavaScript library for interacting with DotCMS REST APIs.", + "repository": { + "type": "git", + "url": "git+https://github.com/dotCMS/core.git#master" + }, + "scripts": { + "build": "nx run sdk-client:build:js; cd ../../../../dotCMS/src/main/webapp/html/js/editor-js; rm -rf src package.json *.esm.d.ts" + }, + "keywords": [ + "dotCMS", + "CMS", + "Content Management", + "API Client", + "REST API" + ], + "author": "dotcms ", + "license": "MIT", + "bugs": { + "url": "https://github.com/dotCMS/core/issues" + }, + "homepage": "https://github.com/dotCMS/core/tree/master/core-web/libs/sdk/client/README.md" } diff --git a/core-web/libs/sdk/experiments/package.json b/core-web/libs/sdk/experiments/package.json index aaf274d22c49..c83c88e4d4e6 100644 --- a/core-web/libs/sdk/experiments/package.json +++ b/core-web/libs/sdk/experiments/package.json @@ -1,30 +1,30 @@ { - "name": "@dotcms/experiments", - "version": "0.0.1-alpha.33", - "description": "Official JavaScript library to use Experiments with DotCMS.", - "repository": { - "type": "git", - "url": "git+https://github.com/dotCMS/core.git#master" - }, - "keywords": [ - "dotCMS", - "CMS", - "Content Management", - "A/B testing", - "Experiments" - ], - "author": "dotcms ", - "license": "MIT", - "bugs": { - "url": "https://github.com/dotCMS/core/issues" - }, - "homepage": "https://github.com/dotCMS/core/tree/master/core-web/libs/sdk/experiments/README.md", - "dependencies": { - "@jitsu/sdk-js": "^3.1.5" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18", - "@dotcms/client": "0.0.1-alpha.33" - } + "name": "@dotcms/experiments", + "version": "0.0.1-alpha.35", + "description": "Official JavaScript library to use Experiments with DotCMS.", + "repository": { + "type": "git", + "url": "git+https://github.com/dotCMS/core.git#master" + }, + "keywords": [ + "dotCMS", + "CMS", + "Content Management", + "A/B testing", + "Experiments" + ], + "author": "dotcms ", + "license": "MIT", + "bugs": { + "url": "https://github.com/dotCMS/core/issues" + }, + "homepage": "https://github.com/dotCMS/core/tree/master/core-web/libs/sdk/experiments/README.md", + "dependencies": { + "@jitsu/sdk-js": "^3.1.5" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18", + "@dotcms/client": "0.0.1-alpha.35" + } } diff --git a/core-web/libs/sdk/react/package.json b/core-web/libs/sdk/react/package.json index 42a0b89afa48..30931a4f917d 100644 --- a/core-web/libs/sdk/react/package.json +++ b/core-web/libs/sdk/react/package.json @@ -1,30 +1,30 @@ { - "name": "@dotcms/react", - "version": "0.0.1-alpha.33", - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18", - "@dotcms/client": "0.0.1-alpha.33", - "@tinymce/tinymce-react": "^5.1.1" - }, - "description": "Official React Components library to render a dotCMS page.", - "repository": { - "type": "git", - "url": "git+https://github.com/dotCMS/core.git#master" - }, - "keywords": [ - "dotCMS", - "CMS", - "Content Management", - "API Client", - "REST API", - "React", - "Components" - ], - "author": "dotcms ", - "license": "MIT", - "bugs": { - "url": "https://github.com/dotCMS/core/issues" - }, - "homepage": "https://github.com/dotCMS/core/tree/master/core-web/libs/sdk/react/README.md" + "name": "@dotcms/react", + "version": "0.0.1-alpha.35", + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18", + "@dotcms/client": "0.0.1-alpha.35", + "@tinymce/tinymce-react": "^5.1.1" + }, + "description": "Official React Components library to render a dotCMS page.", + "repository": { + "type": "git", + "url": "git+https://github.com/dotCMS/core.git#master" + }, + "keywords": [ + "dotCMS", + "CMS", + "Content Management", + "API Client", + "REST API", + "React", + "Components" + ], + "author": "dotcms ", + "license": "MIT", + "bugs": { + "url": "https://github.com/dotCMS/core/issues" + }, + "homepage": "https://github.com/dotCMS/core/tree/master/core-web/libs/sdk/react/README.md" } diff --git a/core-web/libs/sdk/react/src/index.ts b/core-web/libs/sdk/react/src/index.ts index 53c09f8a1ede..6357d6e67223 100644 --- a/core-web/libs/sdk/react/src/index.ts +++ b/core-web/libs/sdk/react/src/index.ts @@ -3,3 +3,5 @@ export * from './lib/components/DotEditableText/DotEditableText'; export * from './lib/components/PageProvider/PageProvider'; export * from './lib/components/Row/Row'; export * from './lib/hooks/useDotcmsPageContext'; +export * from './lib/components/BlockEditorRenderer/BlockEditorRenderer'; +export * from './lib/models/content-node.interface'; diff --git a/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/BlockEditorRenderer.spec.tsx b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/BlockEditorRenderer.spec.tsx new file mode 100644 index 000000000000..645089ba337a --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/BlockEditorRenderer.spec.tsx @@ -0,0 +1,51 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; + +import { BlockEditorRenderer } from './BlockEditorRenderer'; + +import { Block } from '../../models/blocks.interface'; + +describe('BlockEditorRenderer', () => { + const blocks = { + content: [ + { + type: 'paragraph', + attrs: {}, + content: [ + { + type: 'text', + text: 'Hello, World!' + } + ] + } + ] + } as Block; + + it('should render the BlockEditorItem component', () => { + const { getByText } = render(); + expect(getByText('Hello, World!')).toBeInTheDocument(); + }); + + it('should render the custom renderer component', () => { + const customRenderers = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + paragraph: ({ content }: { content: any }) => { + const [{ text }] = content; + + return

{text}

; + } + }; + const { getByTestId } = render( + + ); + expect(getByTestId('custom-paragraph')).toBeInTheDocument(); + }); + + it('should render the property className and style props', () => { + const { container } = render( + + ); + expect(container.firstChild).toHaveClass('test-class'); + expect(container.firstChild).toHaveStyle('color: red'); + }); +}); diff --git a/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/BlockEditorRenderer.tsx b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/BlockEditorRenderer.tsx new file mode 100644 index 000000000000..200a34b1eb51 --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/BlockEditorRenderer.tsx @@ -0,0 +1,35 @@ +import { BlockEditorBlock } from './item/BlockEditorBlock'; + +import { Block } from '../../models/blocks.interface'; +import { CustomRenderer } from '../../models/content-node.interface'; + +export interface BlockEditorRendererProps { + blocks: Block; + customRenderers?: CustomRenderer; + className?: string; + style?: React.CSSProperties; +} + +/** + * BlockEditorRenderer component for rendering block editor field. + * + * @component + * @param {Object} props - The component props. + * @param {Block} props.blocks - The blocks of content to render. + * @param {CustomRenderer} [props.customRenderers] - Optional custom renderers for specific block types. + * @param {string} [props.className] - Optional CSS class name for the container div. + * @param {React.CSSProperties} [props.style] - Optional inline styles for the container div. + * @returns {JSX.Element} A div containing the rendered blocks of content. + */ +export const BlockEditorRenderer = ({ + blocks, + customRenderers, + className, + style +}: BlockEditorRendererProps) => { + return ( +
+ +
+ ); +}; diff --git a/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Code.tsx b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Code.tsx new file mode 100644 index 000000000000..a9ce1942840a --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Code.tsx @@ -0,0 +1,29 @@ +import { BlockProps } from '../../../models/blocks.interface'; +import { CodeBlockProps } from '../../../models/content-node.interface'; + +/** + * Renders a code block component. + * + * @param attrs - The attributes of the code block. + * @param children - The content of the code block. + * @returns The rendered code block component. + */ +export const CodeBlock = ({ attrs, children }: CodeBlockProps) => { + const language = attrs?.language || ''; + + return ( +
+            {children}
+        
+ ); +}; + +/** + * Renders a blockquote component. + * + * @param children - The content to be rendered inside the blockquote. + * @returns The rendered blockquote component. + */ +export const BlockQuote = ({ children }: BlockProps) => { + return
{children}
; +}; diff --git a/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Contentlet.tsx b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Contentlet.tsx new file mode 100644 index 000000000000..2d509c10dfba --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Contentlet.tsx @@ -0,0 +1,61 @@ +import { DotContentProps } from '../../../models/content-node.interface'; + +// Replace this when we have a types lib +export interface Contentlet { + hostName: string; + modDate: string; + publishDate: string; + title: string; + baseType: string; + inode: string; + archived: boolean; + ownerName: string; + host: string; + working: boolean; + locked: boolean; + stInode: string; + contentType: string; + live: boolean; + owner: string; + identifier: string; + publishUserName: string; + publishUser: string; + languageId: number; + creationDate: string; + url: string; + titleImage: string; + modUserName: string; + hasLiveVersion: boolean; + folder: string; + hasTitleImage: boolean; + sortOrder: number; + modUser: string; + __icon__: string; + contentTypeIcon: string; + variant: string; +} + +/** + * Renders the default content for an unknown content type. + */ +const DefaultContent = () =>
Unknown Content Type
; + +/** + * Renders a DotContent component. + * + * @param {DotContentProps} props - The props for the DotContent component. + * @returns {JSX.Element} The rendered DotContent component. + */ +export const DotContent = (props: DotContentProps) => { + const { attrs, customRenderers } = props; + + const data = attrs?.data as unknown as Contentlet; + + const Component = customRenderers?.[data?.contentType] ?? DefaultContent; + + if (!data) { + console.error('DotContent: No data provided'); + } + + return ; +}; diff --git a/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Image.tsx b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Image.tsx new file mode 100644 index 000000000000..e0969c73e6af --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Image.tsx @@ -0,0 +1,18 @@ +import { DotCmsClient } from '@dotcms/client'; + +import { ContentNode, DotCMSImageProps } from '../../../models/content-node.interface'; + +/** + * Renders an image component for dotCMS. + * + * @param props - The props for the DotCMSImage component. + * @returns The rendered image component. + */ +export const DotCMSImage = (props: ContentNode) => { + const { data, src, alt } = props.attrs as DotCMSImageProps; + const client = DotCmsClient.instance; + + const srcUrl = data?.identifier ? `${client.dotcmsUrl}${src}` : src; + + return {alt}; +}; diff --git a/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Lists.tsx b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Lists.tsx new file mode 100644 index 000000000000..f7e48c45f2f7 --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Lists.tsx @@ -0,0 +1,31 @@ +import { BlockProps } from '../../../models/blocks.interface'; + +/** + * ListItem component represents a list item in a block editor. + * + * @param children - The content of the list item. + * @returns The rendered list item element. + */ +export const ListItem = ({ children }: BlockProps) => { + return
  • {children}
  • ; +}; + +/** + * Renders an ordered list component. + * + * @param children - The content to be rendered inside the ordered list. + * @returns The ordered list component. + */ +export const OrderedList = ({ children }: BlockProps) => { + return
      {children}
    ; +}; + +/** + * Renders a bullet list component. + * + * @param children - The content of the bullet list. + * @returns The rendered bullet list component. + */ +export const BulletList = ({ children }: BlockProps) => { + return
      {children}
    ; +}; diff --git a/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Table.tsx b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Table.tsx new file mode 100644 index 000000000000..601948e9c0d6 --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Table.tsx @@ -0,0 +1,60 @@ +import React from 'react'; + +import { ContentNode } from '../../../models/content-node.interface'; + +interface TableRendererProps { + content: ContentNode[]; + blockEditorItem: React.FC<{ + content: ContentNode[]; + }>; +} + +/** + * Renders a table component for the Block Editor. + * + * @param content - The content of the table. + * @param blockEditorItem - The Block Editor item component. + */ +export const TableRenderer: React.FC = ({ + content, + blockEditorItem +}: TableRendererProps) => { + const BlockEditorItemComponent = blockEditorItem; + + const renderTableContent = (node: ContentNode) => { + return ; + }; + + return ( + + + {content.slice(0, 1).map((rowNode, rowIndex) => ( + + {rowNode.content?.map((cellNode, cellIndex) => ( + + ))} + + ))} + + + {content.slice(1).map((rowNode, rowIndex) => ( + + {rowNode.content?.map((cellNode, cellIndex) => ( + + ))} + + ))} + +
    + {renderTableContent(cellNode)} +
    + {renderTableContent(cellNode)} +
    + ); +}; diff --git a/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Texts.tsx b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Texts.tsx new file mode 100644 index 000000000000..c994a3c6f103 --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Texts.tsx @@ -0,0 +1,126 @@ +import { BlockProps } from '../../../models/blocks.interface'; +import { + ContentNode, + HeadingProps, + LinkProps, + ParagraphProps +} from '../../../models/content-node.interface'; + +/** + * Renders the text in bold. + * + * @param children - The content to be rendered in bold. + */ +export const Bold = ({ children }: BlockProps) => {children}; + +/** + * Renders the text in italic format. + * + * @param children - The content to be rendered in italic. + */ +export const Italic = ({ children }: BlockProps) => {children}; + +/** + * Renders a strike-through text. + * + * @param children - The content to be rendered within the strike-through element. + */ +export const Strike = ({ children }: BlockProps) => {children}; + +/** + * Renders an underline element for the given children. + * + * @param children - The content to be underlined. + */ +export const Underline = ({ children }: BlockProps) => {children}; + +/** + * Renders a paragraph element. + * + * @param children - The content of the paragraph. + * @param attrs - The style attributes for the paragraph. + * @returns The rendered paragraph element. + */ +export const Paragraph = ({ children, attrs }: ParagraphProps) => { + return

    {children}

    ; +}; + +/** + * Renders a link component. + * + * @param children - The content of the link. + * @param attrs - The attributes to be applied to the link. + * @returns The rendered link component. + */ +export const Link = ({ children, attrs }: LinkProps) => { + return {children}; +}; + +/** + * Renders a heading element with the specified level. + * + * @param children - The content of the heading. + * @param attrs - The attributes for the heading. + * @returns The rendered heading element. + */ +export const Heading = ({ children, attrs }: HeadingProps) => { + const level = attrs?.level || 1; + const Tag = `h${level}` as keyof JSX.IntrinsicElements; + + return {children}; +}; + +/** + * Renders the superscript text. + * + * @param children - The content to be rendered as superscript. + */ +export const Superscript = ({ children }: BlockProps) => {children}; + +/** + * Renders a subscript element. + * + * @param children - The content to be rendered as subscript. + */ +export const Subscript = ({ children }: BlockProps) => {children}; + +const nodeMarks: Record> = { + link: Link, + bold: Bold, + underline: Underline, + italic: Italic, + strike: Strike, + superscript: Superscript, + subscript: Subscript +}; + +type TextBlockProps = Omit; + +/** + * Renders a text block with optional marks. + * + * @param props - The props for the TextBlock component. + * @returns The rendered text block. + */ +export const TextBlock = (props: TextBlockProps) => { + const { marks = [], text } = props; + const mark = marks[0] || { type: '', attrs: {} }; + const newProps = { ...props, marks: marks.slice(1) }; + const Component = nodeMarks[mark?.type]; + + // To avoid the warning: "Warning: Invalid DOM property `class`. Did you mean `className`?" + if (mark.attrs) { + mark.attrs.className = mark.attrs.class; + delete mark.attrs.class; + } + + if (!Component) { + return text; + } + + return ( + + + + ); +}; diff --git a/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Video.tsx b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Video.tsx new file mode 100644 index 000000000000..7dc4fcf3ee15 --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/blocks/Video.tsx @@ -0,0 +1,26 @@ +import { DotCmsClient } from '@dotcms/client'; + +import { ContentNode, DotCMSVideoProps } from '../../../models/content-node.interface'; + +/** + * Renders a video component for displaying videos. + * + * @param props - The properties for the video component. + * @returns The rendered video component. + */ +export const DotCMSVideo = (props: ContentNode) => { + const { data, src, mimeType, width, height } = props.attrs as DotCMSVideoProps; + const client = DotCmsClient.instance; + + const srcUrl = data?.identifier ? `${client.dotcmsUrl}${src}` : src; + + const poster = data?.thumbnail ? `${client.dotcmsUrl}${data?.thumbnail}` : 'poster-image.jpg'; + + return ( + + ); +}; diff --git a/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/item/BlockEditorBlock.spec.tsx b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/item/BlockEditorBlock.spec.tsx new file mode 100644 index 000000000000..ecd5d1566d62 --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/item/BlockEditorBlock.spec.tsx @@ -0,0 +1,634 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; + +import { DotCmsClient } from '@dotcms/client'; + +import { BlockEditorBlock } from './BlockEditorBlock'; + +import { ContentNode } from '../../../models/content-node.interface'; + +const BLOCKS_MOCKS = { + PARAGRAPH: [ + { + type: 'paragraph', + attrs: {}, + content: [ + { + type: 'text', + text: 'Hello, World!' + } + ] + }, + { + type: 'heading', + attrs: { level: '4' }, + content: [ + { + type: 'text', + text: 'Heading!!' + } + ] + } + ], + LINK: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Link text', + marks: [ + { + type: 'link', + attrs: { + href: 'https://dotcms.com' + } + } + ] + } + ] + } + ], + BOLD: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Bold text', + marks: [ + { + type: 'bold', + attrs: {} + } + ] + } + ] + } + ], + ITALIC: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Italic text', + marks: [ + { + type: 'italic', + attrs: {} + } + ] + } + ] + } + ], + STRIKE: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Strike text', + marks: [ + { + type: 'strike', + attrs: {} + } + ] + } + ] + } + ], + UNDERLINE: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Underline text', + marks: [ + { + type: 'underline', + attrs: {} + } + ] + } + ] + } + ], + SUPSCRIPT: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Superscript text', + marks: [ + { + type: 'superscript', + attrs: {} + } + ] + } + ] + } + ], + SUBSCRIPT: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Subscript text', + marks: [ + { + type: 'subscript', + attrs: {} + } + ] + } + ] + } + ], + LIST: [ + { + type: 'listItem', + content: [ + { + type: 'text', + text: 'Item 1' + } + ] + } + ], + BULLET_LIST: [ + { + type: 'bulletList', + content: [ + { + type: 'listItem', + content: [ + { + type: 'text', + text: 'Item 1' + } + ] + }, + { + type: 'listItem', + content: [ + { + type: 'text', + text: 'Item 2' + } + ] + } + ] + } + ], + ORDERED_LIST: [ + { + type: 'orderedList', + content: [ + { + type: 'listItem', + content: [ + { + type: 'text', + text: 'Item 1' + } + ] + }, + { + type: 'listItem', + content: [ + { + type: 'text', + text: 'Item 2' + } + ] + } + ] + } + ], + BLOCKQUOTE: [ + { + type: 'blockquote', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Blockquote text' + } + ] + } + ] + } + ], + CODE_BLOCK: [ + { + type: 'codeBlock', + attrs: { + language: 'javascript' + }, + content: [ + { + type: 'text', + text: 'console.log("Hello, World!")' + } + ] + } + ], + HARDBREAK: [ + { + type: 'hardBreak', + content: [] + } + ], + HORIZONTAL_RULE: [ + { + type: 'horizontalRule', + content: [] + } + ], + DOT_IMAGE: [ + { + type: 'dotImage', + attrs: { + src: '/image.jpg', + data: { + title: 'Image title', + baseType: 'Image', + inode: '1234', + archived: false, + working: true, + locked: false, + contentType: 'Image', + live: true, + identifier: 'image-identifier', + image: 'image.jpg', + imageContentAsset: 'image-content-asset', + urlTitle: 'image-url-title', + url: 'image-url', + titleImage: 'title-image', + urlMap: 'image-url-map', + hasLiveVersion: true, + hasTitleImage: true, + sortOrder: 1, + modUser: 'admin', + __icon__: 'image-icon', + contentTypeIcon: 'image-content-type-icon' + } + } + } + ], + DOT_IMAGE_EXTERNAL: [ + { + type: 'dotImage', + attrs: { + src: 'https://external.com/image.jpg', + data: {} + } + } + ], + DOT_VIDEO: [ + { + type: 'dotVideo', + attrs: { + src: '/video.mp4', + width: '640', + height: '360', + mimeType: 'video/mp4', + data: { + title: 'Video title', + baseType: 'Video', + inode: '1234', + archived: false, + working: true, + locked: false, + contentType: 'Video', + live: true, + identifier: 'video-identifier', + image: 'video.jpg', + imageContentAsset: 'video-content-asset', + urlTitle: 'video-url-title', + url: 'video-url', + titleImage: 'title-image', + urlMap: 'video-url-map', + hasLiveVersion: true, + hasTitleImage: true, + sortOrder: 1, + modUser: 'admin', + __icon__: 'video-icon', + contentTypeIcon: 'video-content-type-icon', + thumbnail: '/thumbnail.jpg' + } + } + } + ], + TABLE: [ + { + type: 'table', + content: [ + { + type: 'tableRow', + content: [ + { + type: 'tableHeader', + content: [ + [ + { + type: 'paragraph', + attrs: { + textAlign: 'left' + }, + content: [ + { + type: 'text', + text: 'Header 1' + } + ] + } + ] + ] + }, + { + type: 'tableHeader', + content: [ + [ + { + type: 'paragraph', + attrs: { + textAlign: 'left' + }, + content: [ + { + type: 'text', + text: 'Header 1' + } + ] + } + ] + ] + } + ] + }, + { + type: 'tableRow', + content: [ + { + type: 'tableCell', + attrs: { + colspan: 1, + rowspan: 1, + colwidth: null + }, + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left' + }, + content: [ + { + type: 'text', + text: 'Content 1' + } + ] + } + ] + }, + { + type: 'tableCell', + attrs: { + colspan: 1, + rowspan: 1, + colwidth: null + }, + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left' + }, + content: [ + { + type: 'text', + text: 'Content 2' + } + ] + } + ] + } + ] + } + ] + } + ], + DOT_CONTENT: [ + { + type: 'dotContent', + attrs: { + identifier: '1234', + title: 'My activity' + } + } + ] +} as unknown as { [key: string]: ContentNode[] }; + +describe('BlockEditorItem', () => { + it('should render the paragraph block', () => { + const { getByText } = render(); + expect(getByText('Hello, World!')).toBeInTheDocument(); + }); + + it('should render the heading block', () => { + const { container } = render(); + const heading = container.querySelector('h4'); + expect(heading).toBeInTheDocument(); + }); + + describe('should render the text block', () => { + it('should render the text block', () => { + const { getByText } = render(); + expect(getByText('Hello, World!')).toBeInTheDocument(); + }); + + it('should render a link', () => { + const { container } = render(); + const link = container.querySelector('a'); + expect(link).toBeInTheDocument(); + expect(link).toHaveTextContent('Link text'); + expect(link).toHaveAttribute('href', 'https://dotcms.com'); + }); + + it('should render a bold text', () => { + const { container } = render(); + const bold = container.querySelector('strong'); + expect(bold).toBeInTheDocument(); + expect(bold).toHaveTextContent('Bold text'); + }); + + it('should render an italic text', () => { + const { container } = render(); + const italic = container.querySelector('em'); + expect(italic).toBeInTheDocument(); + expect(italic).toHaveTextContent('Italic text'); + }); + + it('should render a strike text', () => { + const { container } = render(); + const strike = container.querySelector('s'); + expect(strike).toBeInTheDocument(); + expect(strike).toHaveTextContent('Strike text'); + }); + + it('should render an underline text', () => { + const { container } = render(); + const underline = container.querySelector('u'); + expect(underline).toBeInTheDocument(); + expect(underline).toHaveTextContent('Underline text'); + }); + + it('should render a supscript text', () => { + const { container } = render(); + const superscript = container.querySelector('sup'); + expect(superscript).toBeInTheDocument(); + expect(superscript).toHaveTextContent('Superscript text'); + }); + + it('should render a subscript text', () => { + const { container } = render(); + const subscript = container.querySelector('sub'); + expect(subscript).toBeInTheDocument(); + expect(subscript).toHaveTextContent('Subscript text'); + }); + }); + + describe('Lists', () => { + const { container } = render(); + const listItem = container.querySelector('li'); + expect(listItem).toBeInTheDocument(); + expect(listItem).toHaveTextContent('Item 1'); + }); + + it('should render a bullet list', () => { + const { container } = render(); + const list = container.querySelector('ul'); + expect(list).toBeInTheDocument(); + expect(list).toHaveTextContent('Item 1'); + expect(list).toHaveTextContent('Item 2'); + }); + + it('should render an ordered list', () => { + const { container } = render(); + const list = container.querySelector('ol'); + expect(list).toBeInTheDocument(); + expect(list).toHaveTextContent('Item 1'); + expect(list).toHaveTextContent('Item 2'); + }); +}); + +it('should render a blockquote', () => { + const { container } = render(); + const blockquote = container.querySelector('blockquote'); + expect(blockquote).toBeInTheDocument(); + expect(blockquote).toHaveTextContent('Blockquote text'); +}); + +it('should render a code block', () => { + const { container } = render(); + const codeBlock = container.querySelector('pre'); + expect(codeBlock).toBeInTheDocument(); + expect(codeBlock).toHaveAttribute('data-language', 'javascript'); + expect(codeBlock).toHaveTextContent('console.log("Hello, World!")'); +}); + +describe('Separators', () => { + it('should render a horizontal rule', () => { + const { container } = render(); + const hr = container.querySelector('hr'); + expect(hr).toBeInTheDocument(); + }); + + it('should render a hard break', () => { + const { container } = render(); + const br = container.querySelector('br'); + expect(br).toBeInTheDocument(); + }); +}); + +describe('Assets', () => { + it('should render a dotImage using internal src', () => { + // Mock DotCmsClient + DotCmsClient.instance = { + dotcmsUrl: 'https://some.dotcms.com' + } as unknown as DotCmsClient; + + const { container } = render(); + const image = container.querySelector('img'); + expect(image).toBeInTheDocument(); + expect(image).toHaveAttribute('src', 'https://some.dotcms.com/image.jpg'); + }); + + it('should render a dotImage using external src', () => { + // Mock DotCmsClient + DotCmsClient.instance = { + dotcmsUrl: 'https://some.dotcms.com' + } as unknown as DotCmsClient; + + const { container } = render( + + ); + const image = container.querySelector('img'); + expect(image).toBeInTheDocument(); + expect(image).toHaveAttribute('src', 'https://external.com/image.jpg'); + }); + + it('should render a dotVideo', () => { + // Mock DotCmsClient + DotCmsClient.instance = { + dotcmsUrl: 'https://some.dotcms.com' + } as unknown as DotCmsClient; + + const { container } = render(); + const video = container.querySelector('video'); + expect(video).toBeInTheDocument(); + expect(video).toHaveAttribute('width', '640'); + expect(video).toHaveAttribute('height', '360'); + expect(video).toHaveAttribute('poster', 'https://some.dotcms.com/thumbnail.jpg'); + + const source = video?.querySelector('source'); + expect(source).toHaveAttribute('src', 'https://some.dotcms.com/video.mp4'); + expect(source).toHaveAttribute('type', 'video/mp4'); + }); +}); + +it('should render a table', () => { + const { container } = render(); + const table = container.querySelector('table'); + expect(table).toBeInTheDocument(); + + const rows = table?.querySelectorAll('tr'); + expect(rows).toHaveLength(2); + + const cells = rows?.[1].querySelectorAll('td'); + expect(cells).toHaveLength(2); + expect(cells?.[0]).toHaveTextContent('Content 1'); +}); + +it('should render a dotContent from customRenderers', () => { + const customRenderers = { + dotContent: ({ title }: { title: string }) => { + return
    {title}
    ; + } + }; + + const { getByTestId } = render( + + ); + expect(getByTestId('custom-dot-content')).toHaveTextContent('My activity'); +}); diff --git a/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/item/BlockEditorBlock.tsx b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/item/BlockEditorBlock.tsx new file mode 100644 index 000000000000..26cfd1dfc892 --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/item/BlockEditorBlock.tsx @@ -0,0 +1,146 @@ +import { Blocks } from '../../../models/blocks.interface'; +import { ContentNode, CustomRenderer } from '../../../models/content-node.interface'; +import { BlockQuote, CodeBlock } from '../blocks/Code'; +import { DotContent } from '../blocks/Contentlet'; +import { DotCMSImage } from '../blocks/Image'; +import { BulletList, ListItem, OrderedList } from '../blocks/Lists'; +import { TableRenderer } from '../blocks/Table'; +import { Heading, Paragraph, TextBlock } from '../blocks/Texts'; +import { DotCMSVideo } from '../blocks/Video'; + +/** + * Renders a block editor item based on the provided content and custom renderers. + * + * @param content - The content nodes to render. + * @param customRenderers - Optional custom renderers for specific node types. + * @returns The rendered block editor item. + */ +export const BlockEditorBlock = ({ + content, + customRenderers +}: { + content: ContentNode[]; + customRenderers?: CustomRenderer; +}) => { + return content?.map((node: ContentNode, index) => { + const CustomRendererComponent = customRenderers?.[node.type]; + if (CustomRendererComponent) { + return ( + + + + ); + } + + switch (node.type) { + case Blocks.PARAGRAPH: + return ( + + + + ); + + case Blocks.HEADING: + return ( + + + + ); + + case Blocks.TEXT: + return ; + + case Blocks.BULLET_LIST: + return ( + + + + ); + + case Blocks.ORDERED_LIST: + return ( + + + + ); + + case Blocks.LIST_ITEM: + return ( + + + + ); + + case Blocks.BLOCK_QUOTE: + return ( +
    + +
    + ); + + case Blocks.CODE_BLOCK: + return ( + + + + ); + + case Blocks.HARDBREAK: + return
    ; + + case Blocks.HORIZONTAL_RULE: + return
    ; + + case Blocks.DOT_IMAGE: + return ; + + case Blocks.DOT_VIDEO: + return ; + + case Blocks.TABLE: + return ( + + ); + + case Blocks.DOT_CONTENT: + return ( + + ); + + default: + return
    Unknown Block Type {node.type}
    ; + } + }); +}; diff --git a/core-web/libs/sdk/react/src/lib/models/blocks.interface.ts b/core-web/libs/sdk/react/src/lib/models/blocks.interface.ts new file mode 100644 index 000000000000..74d99737131b --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/models/blocks.interface.ts @@ -0,0 +1,57 @@ +import { ContentNode } from './content-node.interface'; + +export interface Block { + content: ContentNode[]; +} + +export interface DotContentletProps { + title: string; + baseType: string; + inode: string; + archived: boolean; + working: boolean; + locked: boolean; + contentType: string; + live: boolean; + identifier: string; + image: string; + imageContentAsset: string; + urlTitle: string; + url: string; + titleImage: string; + urlMap: string; + hasLiveVersion: boolean; + hasTitleImage: boolean; + sortOrder: number; + modUser: string; + __icon__: string; + contentTypeIcon: string; + language: string; + description: string; + shortDescription: string; + salePrice: string; + retailPrice: string; + mimeType: string; + thumbnail?: string; +} + +export interface BlockProps { + children: React.ReactNode; +} + +export enum Blocks { + PARAGRAPH = 'paragraph', + HEADING = 'heading', + TEXT = 'text', + BULLET_LIST = 'bulletList', + ORDERED_LIST = 'orderedList', + LIST_ITEM = 'listItem', + BLOCK_QUOTE = 'blockquote', + CODE_BLOCK = 'codeBlock', + HARDBREAK = 'hardBreak', + HORIZONTAL_RULE = 'horizontalRule', + DOT_IMAGE = 'dotImage', + DOT_VIDEO = 'dotVideo', + TABLE = 'table', + DOT_CONTENT = 'dotContent' +} diff --git a/core-web/libs/sdk/react/src/lib/models/content-node.interface.ts b/core-web/libs/sdk/react/src/lib/models/content-node.interface.ts new file mode 100644 index 000000000000..973abfa3b89f --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/models/content-node.interface.ts @@ -0,0 +1,37 @@ +import { BlockProps } from './blocks.interface'; + +export interface Mark { + type: string; + attrs: Record; +} + +export interface ContentNode { + type: string; + content: ContentNode[]; + attrs?: Record; + marks?: Mark[]; + text?: string; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type CustomRenderer = Record>; + +export type CodeBlockProps = BlockProps & ContentNode; + +export type HeadingProps = BlockProps & ContentNode; + +export type LinkProps = BlockProps & { attrs?: Mark['attrs'] }; + +export type ParagraphProps = BlockProps & ContentNode; + +export type DotCMSVideoProps = ContentNode['attrs'] & { + data?: Record; +}; + +export type DotCMSImageProps = ContentNode['attrs'] & { + data?: Record; +}; + +export type DotContentProps = ContentNode & { + customRenderers?: CustomRenderer; +}; diff --git a/examples/angular/package.json b/examples/angular/package.json index 06158d16dec5..85644c50709d 100644 --- a/examples/angular/package.json +++ b/examples/angular/package.json @@ -18,8 +18,8 @@ "@angular/platform-browser": "^17.1.0", "@angular/platform-browser-dynamic": "^17.1.0", "@angular/router": "^17.1.0", - "@dotcms/angular": "0.0.1-alpha.33", - "@dotcms/client": "0.0.1-alpha.33", + "@dotcms/angular": "0.0.1-alpha.35", + "@dotcms/client": "0.0.1-alpha.35", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.2" diff --git a/examples/nextjs/jsconfig.json b/examples/nextjs/jsconfig.json index 0d856efda589..c23be37b5361 100644 --- a/examples/nextjs/jsconfig.json +++ b/examples/nextjs/jsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], } } } diff --git a/examples/nextjs/package-lock.json b/examples/nextjs/package-lock.json index 7dd78e52b052..6b2d41b01204 100644 --- a/examples/nextjs/package-lock.json +++ b/examples/nextjs/package-lock.json @@ -8,9 +8,9 @@ "name": "nextjs-dotcms-ema", "version": "0.1.0", "dependencies": { - "@dotcms/client": "0.0.1-alpha.30", - "@dotcms/experiments": "0.0.1-alpha.30", - "@dotcms/react": "0.0.1-alpha.30", + "@dotcms/client": "0.0.1-alpha.31", + "@dotcms/experiments": "0.0.1-alpha.31", + "@dotcms/react": "0.0.1-alpha.31", "next": "14.1.1", "react": "^18", "react-dom": "^18" @@ -49,29 +49,29 @@ } }, "node_modules/@dotcms/client": { - "version": "0.0.1-alpha.30", - "resolved": "https://registry.npmjs.org/@dotcms/client/-/client-0.0.1-alpha.30.tgz", - "integrity": "sha512-T+wgunuu7iwQXpQSNncWfXc85HjlGHfxA1tgLtRthteSataKmY5oI/Ki0xNvxHLYnA1mklcLTRgFKzBHkjLoJw==" + "version": "0.0.1-alpha.31", + "resolved": "https://registry.npmjs.org/@dotcms/client/-/client-0.0.1-alpha.31.tgz", + "integrity": "sha512-FD9c8j20nTNY+ATlQHuESjBg+5DkyX0idTBCLMOadZJmNftIf5niDHd1y6VMS+p0xN3RyKeBxYIeUsH7YTOU5g==" }, "node_modules/@dotcms/experiments": { - "version": "0.0.1-alpha.30", - "resolved": "https://registry.npmjs.org/@dotcms/experiments/-/experiments-0.0.1-alpha.30.tgz", - "integrity": "sha512-iTf6b3PlwH7P+4uHDpaV8ctIQeP7BH5DaplW/fhAczaf/s767OtnyS8liK3BBdikzVlE0UYuPcV9ZZ/u826pyg==", + "version": "0.0.1-alpha.31", + "resolved": "https://registry.npmjs.org/@dotcms/experiments/-/experiments-0.0.1-alpha.31.tgz", + "integrity": "sha512-dKsbHTw7qsmBjJ67z8oZV6SpfKPeB9XbiC7rfm1hyh30BnBwpBrKOSuzSLXLSpQVONj1uteKqGHar/nvGIAIOA==", "dependencies": { "@jitsu/sdk-js": "^3.1.5" }, "peerDependencies": { - "@dotcms/client": "0.0.1-alpha.30", + "@dotcms/client": "0.0.1-alpha.31", "react": ">=18", "react-dom": ">=18" } }, "node_modules/@dotcms/react": { - "version": "0.0.1-alpha.30", - "resolved": "https://registry.npmjs.org/@dotcms/react/-/react-0.0.1-alpha.30.tgz", - "integrity": "sha512-GyVKQdhOyHk67xgbKkVOAXkpOtF3hQqYCWE9gRgHR0OdBbgJJkUhAsJqyUYKtasZ4krwt7jpG1qhlAr3woN5kA==", + "version": "0.0.1-alpha.31", + "resolved": "https://registry.npmjs.org/@dotcms/react/-/react-0.0.1-alpha.31.tgz", + "integrity": "sha512-NDH9TeasIImm+Jc5j7TNJFaeix7qie+pTXFbBGZdmSRr2gZyBuVTeujjVVtLLy4DNj/B8J38pqFlA0urlRTQsA==", "peerDependencies": { - "@dotcms/client": "0.0.1-alpha.30", + "@dotcms/client": "0.0.1-alpha.31", "react": ">=18", "react-dom": ">=18" } diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index 01b6d26fbb08..668481704b5a 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -1,27 +1,27 @@ { - "name": "nextjs-dotcms-ema", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@dotcms/client": "0.0.1-alpha.33", - "@dotcms/react": "0.0.1-alpha.33", - "@dotcms/experiments": "0.0.1-alpha.33", - "next": "14.1.1", - "react": "^18", - "react-dom": "^18" - }, - "devDependencies": { - "@tailwindcss/typography": "^0.5.13", - "autoprefixer": "^10", - "eslint": "^8", - "eslint-config-next": "14.0.4", - "postcss": "^8", - "tailwindcss": "^3" - } + "name": "nextjs-dotcms-ema", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@dotcms/client": "0.0.1-alpha.35", + "@dotcms/react": "0.0.1-alpha.35", + "@dotcms/experiments": "0.0.1-alpha.35", + "next": "14.1.1", + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "@tailwindcss/typography": "^0.5.13", + "autoprefixer": "^10", + "eslint": "^8", + "eslint-config-next": "14.0.4", + "postcss": "^8", + "tailwindcss": "^3" + } } diff --git a/examples/nextjs/src/app/globals.css b/examples/nextjs/src/app/globals.css index bdd79e7d0d49..f801db716a54 100644 --- a/examples/nextjs/src/app/globals.css +++ b/examples/nextjs/src/app/globals.css @@ -17,3 +17,12 @@ overflow: hidden; } } + +/* Custom classes, example to BlockEditorRenderer */ +table, th, td { + border: 1px solid; +} + +.blocks { + border: 5px solid red; +} \ No newline at end of file diff --git a/examples/nextjs/src/components/content-types/blog.js b/examples/nextjs/src/components/content-types/blog.js new file mode 100644 index 000000000000..c77afc15a5e4 --- /dev/null +++ b/examples/nextjs/src/components/content-types/blog.js @@ -0,0 +1,32 @@ +import { BlockEditorRenderer } from "@dotcms/react"; + +const CustomParagraph = ({content}) => { + if (!content) { + return null; + } + const [{ text }] = content; + return

    {text}

    +} + +const ActivityBlock = (data) => { + const { title, description } = data; + + return (
    +

    {title}

    +

    {description}

    +
    ) +} + + +function BlogWithBlockEditor({blockEditorItem}){ + return +} +export default BlogWithBlockEditor; \ No newline at end of file diff --git a/examples/nextjs/src/components/my-page.js b/examples/nextjs/src/components/my-page.js index d60d5d137b31..a61cf3b9c52b 100644 --- a/examples/nextjs/src/components/my-page.js +++ b/examples/nextjs/src/components/my-page.js @@ -17,6 +17,7 @@ import { withExperiments } from "@dotcms/experiments"; import { CustomNoComponent } from "./content-types/empty"; import { usePageAsset } from "../hooks/usePageAsset"; +import BlogWithBlockEditor from "./content-types/blog"; import { DotCmsClient } from "@dotcms/client"; /** @@ -39,6 +40,7 @@ const componentsMap = { calendarEvent: CalendarEvent, CallToAction: CallToAction, CustomNoComponent: CustomNoComponent, + BlockEditorItem: BlogWithBlockEditor }; export function MyPage({ pageAsset, nav }) { diff --git a/examples/vuejs/package.json b/examples/vuejs/package.json index 4a353985d8fa..673b2d2a1bd0 100644 --- a/examples/vuejs/package.json +++ b/examples/vuejs/package.json @@ -1,31 +1,31 @@ { - "name": "vuejs", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore", - "format": "prettier --write src/" - }, - "dependencies": { - "@dotcms/client": "^0.0.1-alpha.33", - "vue": "^3.4.15", - "vue-router": "^4.2.5" - }, - "devDependencies": { - "@rushstack/eslint-patch": "^1.3.3", - "@vitejs/plugin-vue": "^5.0.3", - "@vitejs/plugin-vue-jsx": "^3.1.0", - "@vue/eslint-config-prettier": "^8.0.0", - "autoprefixer": "^10.4.17", - "eslint": "^8.49.0", - "eslint-plugin-vue": "^9.17.0", - "postcss": "^8.4.35", - "prettier": "^3.0.3", - "tailwindcss": "^3.4.1", - "vite": "^5.0.11" - } + "name": "vuejs", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore", + "format": "prettier --write src/" + }, + "dependencies": { + "@dotcms/client": "0.0.1-alpha.35", + "vue": "^3.4.15", + "vue-router": "^4.2.5" + }, + "devDependencies": { + "@rushstack/eslint-patch": "^1.3.3", + "@vitejs/plugin-vue": "^5.0.3", + "@vitejs/plugin-vue-jsx": "^3.1.0", + "@vue/eslint-config-prettier": "^8.0.0", + "autoprefixer": "^10.4.17", + "eslint": "^8.49.0", + "eslint-plugin-vue": "^9.17.0", + "postcss": "^8.4.35", + "prettier": "^3.0.3", + "tailwindcss": "^3.4.1", + "vite": "^5.0.11" + } }