diff --git a/core-web/libs/sdk/angular/package.json b/core-web/libs/sdk/angular/package.json index 477fcedcdf0d..e8d0e48b69ab 100644 --- a/core-web/libs/sdk/angular/package.json +++ b/core-web/libs/sdk/angular/package.json @@ -1,14 +1,17 @@ { "name": "@dotcms/angular", - "version": "0.0.1-alpha.32", + "version": "0.0.1-alpha.33", + "dependencies": { + "@tinymce/tinymce-angular": "^8.0.0" + }, "peerDependencies": { "@angular/common": "^17.1.0", "@angular/core": "^17.1.0", "@angular/router": "^17.1.0", - "@dotcms/client": "0.0.1-alpha.32", - "@tinymce/tinymce-angular": "^8.0.0", + "@dotcms/client": "0.0.1-alpha.33", "rxjs": "^7.8.0" }, + "allowedNonPeerDependencies": ["@tinymce/tinymce-angular"], "description": "Official Angular Components library to render a dotCMS page.", "repository": { "type": "git", diff --git a/core-web/libs/sdk/angular/src/lib/components/dot-editable-text/dot-editable-text.component.ts b/core-web/libs/sdk/angular/src/lib/components/dot-editable-text/dot-editable-text.component.ts index 67bdc3d2db94..0e50404fb0f2 100644 --- a/core-web/libs/sdk/angular/src/lib/components/dot-editable-text/dot-editable-text.component.ts +++ b/core-web/libs/sdk/angular/src/lib/components/dot-editable-text/dot-editable-text.component.ts @@ -22,7 +22,7 @@ import { postMessageToEditor } from '@dotcms/client'; -import { TINYMCE_CONFIG, TINYMCE_FORMAT, TINYMCE_MODE } from './utils'; +import { TINYMCE_CONFIG, DOT_EDITABLE_TEXT_FORMAT, DOT_EDITABLE_TEXT_MODE } from './utils'; import { DotCMSContentlet } from '../../models'; @@ -56,17 +56,17 @@ export class DotEditableTextComponent implements OnInit, OnChanges { /** * Represents the mode of the editor which can be `plain`, `minimal`, or `full` * - * @type {TINYMCE_MODE} + * @type {DOT_EDITABLE_TEXT_MODE} * @memberof DotEditableTextComponent */ - @Input() mode: TINYMCE_MODE = 'plain'; + @Input() mode: DOT_EDITABLE_TEXT_MODE = 'plain'; /** * Represents the format of the editor which can be `text` or `html` * - * @type {TINYMCE_FORMAT} + * @type {DOT_EDITABLE_TEXT_FORMAT} * @memberof DotEditableTextComponent */ - @Input() format: TINYMCE_FORMAT = 'text'; + @Input() format: DOT_EDITABLE_TEXT_FORMAT = 'text'; /** * Represents the `contentlet` that can be inline edited * @@ -126,7 +126,7 @@ export class DotEditableTextComponent implements OnInit, OnChanges { * @memberof DotEditableTextComponent */ get onNumberOfPages() { - return this.contentlet['onNumberOfPages']; + return this.contentlet['onNumberOfPages'] || 1; } /** diff --git a/core-web/libs/sdk/angular/src/lib/components/dot-editable-text/utils.ts b/core-web/libs/sdk/angular/src/lib/components/dot-editable-text/utils.ts index c090da24553e..bff68d2c9ad4 100644 --- a/core-web/libs/sdk/angular/src/lib/components/dot-editable-text/utils.ts +++ b/core-web/libs/sdk/angular/src/lib/components/dot-editable-text/utils.ts @@ -1,8 +1,8 @@ import { EditorComponent } from '@tinymce/tinymce-angular'; -export type TINYMCE_MODE = 'minimal' | 'full' | 'plain'; +export type DOT_EDITABLE_TEXT_MODE = 'minimal' | 'full' | 'plain'; -export type TINYMCE_FORMAT = 'html' | 'text'; +export type DOT_EDITABLE_TEXT_FORMAT = 'html' | 'text'; const DEFAULT_TINYMCE_CONFIG: EditorComponent['init'] = { menubar: false, @@ -17,7 +17,7 @@ const DEFAULT_TINYMCE_CONFIG: EditorComponent['init'] = { }; export const TINYMCE_CONFIG: { - [key in TINYMCE_MODE]: EditorComponent['init']; + [key in DOT_EDITABLE_TEXT_MODE]: EditorComponent['init']; } = { minimal: { ...DEFAULT_TINYMCE_CONFIG, diff --git a/core-web/libs/sdk/client/package.json b/core-web/libs/sdk/client/package.json index 3c049aa9e858..b3c43998a5f5 100644 --- a/core-web/libs/sdk/client/package.json +++ b/core-web/libs/sdk/client/package.json @@ -1,6 +1,6 @@ { "name": "@dotcms/client", - "version": "0.0.1-alpha.32", + "version": "0.0.1-alpha.33", "description": "Official JavaScript library for interacting with DotCMS REST APIs.", "repository": { "type": "git", diff --git a/core-web/libs/sdk/experiments/package.json b/core-web/libs/sdk/experiments/package.json index 2eed7e32c417..aaf274d22c49 100644 --- a/core-web/libs/sdk/experiments/package.json +++ b/core-web/libs/sdk/experiments/package.json @@ -1,6 +1,6 @@ { "name": "@dotcms/experiments", - "version": "0.0.1-alpha.32", + "version": "0.0.1-alpha.33", "description": "Official JavaScript library to use Experiments with DotCMS.", "repository": { "type": "git", @@ -25,6 +25,6 @@ "peerDependencies": { "react": ">=18", "react-dom": ">=18", - "@dotcms/client": "0.0.1-alpha.32" + "@dotcms/client": "0.0.1-alpha.33" } } diff --git a/core-web/libs/sdk/react/package.json b/core-web/libs/sdk/react/package.json index 598add55b570..1382b0b55f24 100644 --- a/core-web/libs/sdk/react/package.json +++ b/core-web/libs/sdk/react/package.json @@ -1,10 +1,13 @@ { "name": "@dotcms/react", - "version": "0.0.1-alpha.32", + "version": "0.0.1-alpha.33", + "dependencies": { + "@tinymce/tinymce-react": "^5.1.1" + }, "peerDependencies": { "react": ">=18", "react-dom": ">=18", - "@dotcms/client": "0.0.1-alpha.32" + "@dotcms/client": "0.0.1-alpha.33" }, "description": "Official React Components library to render a dotCMS page.", "repository": { diff --git a/core-web/libs/sdk/react/src/index.ts b/core-web/libs/sdk/react/src/index.ts index 0e77739be956..53c09f8a1ede 100644 --- a/core-web/libs/sdk/react/src/index.ts +++ b/core-web/libs/sdk/react/src/index.ts @@ -1,4 +1,5 @@ export * from './lib/components/DotcmsLayout/DotcmsLayout'; +export * from './lib/components/DotEditableText/DotEditableText'; export * from './lib/components/PageProvider/PageProvider'; export * from './lib/components/Row/Row'; export * from './lib/hooks/useDotcmsPageContext'; diff --git a/core-web/libs/sdk/react/src/lib/components/DotEditableText/DotEditableText.spec.tsx b/core-web/libs/sdk/react/src/lib/components/DotEditableText/DotEditableText.spec.tsx new file mode 100644 index 000000000000..0aaa79b19335 --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/DotEditableText/DotEditableText.spec.tsx @@ -0,0 +1,232 @@ +import { expect } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react'; +import * as tinymceReact from '@tinymce/tinymce-react'; + +import * as dotcmsClient from '@dotcms/client'; + +import { DotEditableText } from './DotEditableText'; + +import { dotcmsContentletMock } from '../../mocks/mockPageContext'; + +const { CUSTOMER_ACTIONS, postMessageToEditor } = dotcmsClient; + +// Define mockEditor before using it in jest.mock +const TINYMCE_EDITOR_MOCK = { + focus: () => { + /* */ + }, + getContent: (_data: string) => '', + isDirty: () => false, + hasFocus: () => false, + setContent: () => { + /* */ + } +}; + +jest.mock('@tinymce/tinymce-react', () => ({ + Editor: jest.fn(({ onInit, onMouseDown, onFocusOut }) => { + onInit({}, TINYMCE_EDITOR_MOCK); + + return
; + }) +})); + +// Mock @dotcms/client module +jest.mock('@dotcms/client', () => ({ + ...jest.requireActual('@dotcms/client'), + isInsideEditor: jest.fn().mockImplementation(() => true), + postMessageToEditor: jest.fn(), + DotCmsClient: { + dotcmsUrl: 'http://localhost:8080' + } +})); + +const mockedDotcmsClient = dotcmsClient as jest.Mocked; +const { Editor } = tinymceReact as jest.Mocked; + +describe('DotEditableText', () => { + describe('Outside editor', () => { + beforeEach(() => { + mockedDotcmsClient.isInsideEditor.mockReturnValue(false); + render(); + }); + + it('should render the content', () => { + const editor = screen.queryByTestId('tinymce-editor'); + expect(editor).toBeNull(); + expect(screen.getByText(dotcmsContentletMock['title'])).not.toBeNull(); + }); + }); + + describe('Inside editor', () => { + let rerenderFn: (ui: React.ReactNode) => void; + + beforeEach(() => { + mockedDotcmsClient.isInsideEditor.mockReturnValue(true); + const { rerender } = render( + + ); + rerenderFn = rerender; + }); + + it('should pass the correct props to the Editor component', () => { + const editor = screen.getByTestId('tinymce-editor'); + expect(editor).not.toBeNull(); + + expect(Editor).toHaveBeenCalledWith( + { + tinymceScriptSrc: 'http://localhost:8080/ext/tinymcev7/tinymce.min.js', + inline: true, + init: { + inline: true, + menubar: false, + plugins: '', + powerpaste_html_import: 'clean', + powerpaste_word_import: 'clean', + suffix: '.min', + toolbar: '', + valid_styles: { + '*': 'font-size,font-family,color,text-decoration,text-align' + } + }, + initialValue: dotcmsContentletMock.title, + onMouseDown: expect.any(Function), + onFocusOut: expect.any(Function), + onInit: expect.any(Function) + }, + {} + ); + }); + + describe('DotEditableText events', () => { + let focusSpy: jest.SpyInstance; + + describe('Window Message', () => { + beforeEach(() => { + focusSpy = jest.spyOn(TINYMCE_EDITOR_MOCK, 'focus'); + }); + + it("should focus on the editor when the message is 'COPY_CONTENTLET_INLINE_EDITING_SUCCESS'", () => { + window.dispatchEvent( + new MessageEvent('message', { + data: { + name: 'COPY_CONTENTLET_INLINE_EDITING_SUCCESS', + payload: { + oldInode: dotcmsContentletMock['inode'], + inode: '456' + } + } + }) + ); + expect(focusSpy).toHaveBeenCalled(); + }); + + it("should not focus on the editor when the message is not 'COPY_CONTENTLET_INLINE_EDITING_SUCCESS'", () => { + window.dispatchEvent( + new MessageEvent('message', { + data: { name: 'ANOTHER_EVENT' } + }) + ); + expect(focusSpy).not.toHaveBeenCalled(); + }); + }); + + describe('mousedown', () => { + const event = new MouseEvent('mousedown', { + bubbles: true + }); + const mutiplePagesContentlet = { + ...dotcmsContentletMock, + onNumberOfPages: 2 + }; + + it('should postMessage the UVE if the content is in multiple pages', () => { + rerenderFn( + + ); + const editorElem = screen.getByTestId('tinymce-editor'); + fireEvent(editorElem, event); + + const payload = { + dataset: { + fieldName: 'title', + inode: mutiplePagesContentlet.inode, + language: mutiplePagesContentlet.languageId + } + }; + expect(postMessageToEditor).toHaveBeenCalledWith({ + action: CUSTOMER_ACTIONS.COPY_CONTENTLET_INLINE_EDITING, + payload + }); + }); + + it('should not postMessage the UVE if the content is in a single page', () => { + const editorElem = screen.getByTestId('tinymce-editor'); + fireEvent(editorElem, event); + expect(postMessageToEditor).not.toHaveBeenCalled(); + }); + }); + + describe('onFocusOut', () => { + let isDirtySpy: jest.SpyInstance; + let getContentSpy: jest.SpyInstance; + + const event = new FocusEvent('focusout', { + bubbles: true + }); + + beforeEach(() => { + isDirtySpy = jest.spyOn(TINYMCE_EDITOR_MOCK, 'isDirty'); + getContentSpy = jest.spyOn(TINYMCE_EDITOR_MOCK, 'getContent'); + }); + + it('should not postMessage the UVE if the editor is not dirty', () => { + mockedDotcmsClient.isInsideEditor.mockReturnValue(false); + const editorElem = screen.getByTestId('tinymce-editor'); + fireEvent(editorElem, event); + expect(isDirtySpy).toHaveBeenCalled(); + expect(getContentSpy).toHaveBeenCalledWith({ format: 'text' }); + expect(postMessageToEditor).not.toHaveBeenCalled(); + }); + + it('should not postMessage the UVE if the content did not change', () => { + isDirtySpy.mockReturnValue(true); + getContentSpy.mockReturnValue(dotcmsContentletMock.title); + + const editorElem = screen.getByTestId('tinymce-editor'); + fireEvent(editorElem, event); + + expect(isDirtySpy).toHaveBeenCalled(); + expect(getContentSpy).toHaveBeenCalledWith({ format: 'text' }); + expect(postMessageToEditor).not.toHaveBeenCalled(); + }); + + it('should postMessage the UVE if the content changed', () => { + isDirtySpy.mockReturnValue(true); + getContentSpy.mockReturnValue('New content'); + + const editorElem = screen.getByTestId('tinymce-editor'); + fireEvent(editorElem, event); + + const postMessageData = { + action: CUSTOMER_ACTIONS.UPDATE_CONTENTLET_INLINE_EDITING, + payload: { + content: 'New content', + dataset: { + inode: dotcmsContentletMock.inode, + langId: dotcmsContentletMock.languageId, + fieldName: 'title' + } + } + }; + + expect(isDirtySpy).toHaveBeenCalled(); + expect(getContentSpy).toHaveBeenCalledWith({ format: 'text' }); + expect(postMessageToEditor).toHaveBeenCalledWith(postMessageData); + }); + }); + }); + }); + + afterEach(() => jest.clearAllMocks()); // Clear all mocks to avoid side effects from other tests +}); diff --git a/core-web/libs/sdk/react/src/lib/components/DotEditableText/DotEditableText.tsx b/core-web/libs/sdk/react/src/lib/components/DotEditableText/DotEditableText.tsx new file mode 100644 index 000000000000..5799e525fe15 --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/DotEditableText/DotEditableText.tsx @@ -0,0 +1,141 @@ +import { Editor } from '@tinymce/tinymce-react'; +import { useEffect, useRef, useState } from 'react'; + +import { + isInsideEditor as isInsideEditorFn, + postMessageToEditor, + CUSTOMER_ACTIONS, + DotCmsClient +} from '@dotcms/client'; + +import { DotEditableTextProps, TINYMCE_CONFIG } from './utils'; + +const MCE_URL = '/ext/tinymcev7/tinymce.min.js'; + +export function DotEditableText({ + mode = 'plain', + format = 'text', + contentlet, + fieldName = '' +}: Readonly) { + const editorRef = useRef(null); + const [scriptSrc, setScriptSrc] = useState(''); + const [isInsideEditor, setIsInsideEditor] = useState(false); + const [content, setContent] = useState(contentlet?.[fieldName] || ''); + + useEffect(() => { + setIsInsideEditor(isInsideEditorFn()); + + if (!contentlet || !fieldName) { + console.error('DotEditableText: contentlet or fieldName is missing'); + console.error('Ensure that all needed props are passed to view and edit the content'); + + return; + } + + if (!isInsideEditorFn()) { + return; + } + + const createURL = new URL(MCE_URL, DotCmsClient.dotcmsUrl); + setScriptSrc(createURL.toString()); + + const content = contentlet?.[fieldName] || ''; + editorRef.current?.setContent(content, { format }); + setContent(content); + }, [format, fieldName, contentlet?.[fieldName]]); + + useEffect(() => { + if (!isInsideEditorFn()) { + return; + } + + const onMessage = ({ data }: MessageEvent) => { + const { name, payload } = data; + if (name !== 'COPY_CONTENTLET_INLINE_EDITING_SUCCESS') { + return; + } + + const { oldInode, inode } = payload; + const currentInode = contentlet.inode; + const shouldFocus = currentInode === oldInode || currentInode === inode; + + if (shouldFocus) { + editorRef.current?.focus(); + } + }; + + window.addEventListener('message', onMessage); + + return () => { + window.removeEventListener('message', onMessage); + }; + }, [contentlet.inode]); + + const onMouseDown = (event: MouseEvent) => { + const { onNumberOfPages = 1 } = contentlet; + const { inode, languageId: language } = contentlet; + + if (onNumberOfPages <= 1 || editorRef.current?.hasFocus()) { + return; + } + + event.stopPropagation(); + event.preventDefault(); + + postMessageToEditor({ + action: CUSTOMER_ACTIONS.COPY_CONTENTLET_INLINE_EDITING, + payload: { + dataset: { + inode, + language, + fieldName + } + } + }); + }; + + const onFocusOut = () => { + const editedContent = editorRef.current?.getContent({ format: format }) || ''; + const { inode, languageId: langId } = contentlet; + + if (!editorRef.current?.isDirty() || !didContentChange(editedContent)) { + return; + } + + postMessageToEditor({ + action: CUSTOMER_ACTIONS.UPDATE_CONTENTLET_INLINE_EDITING, + payload: { + content: editedContent, + dataset: { + inode, + langId, + fieldName + } + } + }); + }; + + const didContentChange = (editedContent: string) => { + return content !== editedContent; + }; + + if (!isInsideEditor) { + // We can let the user pass the Child Component and create a root to get the HTML for the editor + return ; + } + + return ( + (editorRef.current = editor)} + init={TINYMCE_CONFIG[mode]} + initialValue={content} + onMouseDown={onMouseDown} + onFocusOut={onFocusOut} + /> + ); +} + +export default DotEditableText; diff --git a/core-web/libs/sdk/react/src/lib/components/DotEditableText/utils.ts b/core-web/libs/sdk/react/src/lib/components/DotEditableText/utils.ts new file mode 100644 index 000000000000..dc097d6276f8 --- /dev/null +++ b/core-web/libs/sdk/react/src/lib/components/DotEditableText/utils.ts @@ -0,0 +1,82 @@ +import { IAllProps } from '@tinymce/tinymce-react'; + +import { DotCMSContentlet } from '../../models'; + +export type DOT_EDITABLE_TEXT_FORMAT = 'html' | 'text'; + +export type DOT_EDITABLE_TEXT_MODE = 'minimal' | 'full' | 'plain'; + +export interface DotEditableTextProps { + /** + * Represents the field name of the `contentlet` that can be edited + * + * @memberof DotEditableTextProps + */ + fieldName: string; + /** + * Represents the format of the editor which can be `text` or `html` + * + * @type {DOT_EDITABLE_TEXT_FORMAT} + * @memberof DotEditableTextProps + */ + format?: DOT_EDITABLE_TEXT_FORMAT; + /** + * Represents the mode of the editor which can be `plain`, `minimal`, or `full` + * + * @type {DOT_EDITABLE_TEXT_MODE} + * @memberof DotEditableTextProps + */ + mode?: DOT_EDITABLE_TEXT_MODE; + /** + * Represents the `contentlet` that can be inline edited + * + * @type {DotCMSContentlet} + * @memberof DotEditableTextProps + */ + contentlet: DotCMSContentlet; +} + +const DEFAULT_TINYMCE_CONFIG: IAllProps['init'] = { + inline: true, + menubar: false, + powerpaste_html_import: 'clean', + powerpaste_word_import: 'clean', + suffix: '.min', + valid_styles: { + '*': 'font-size,font-family,color,text-decoration,text-align' + } +}; + +export const TINYMCE_CONFIG: { + [key in DOT_EDITABLE_TEXT_MODE]: IAllProps['init']; +} = { + full: { + ...DEFAULT_TINYMCE_CONFIG, + plugins: 'link lists autolink charmap', + toolbar: [ + 'styleselect undo redo | bold italic underline | forecolor backcolor | alignleft aligncenter alignright alignfull | numlist bullist outdent indent | hr charmap removeformat | link' + ], + style_formats: [ + { title: 'Paragraph', format: 'p' }, + { title: 'Header 1', format: 'h1' }, + { title: 'Header 2', format: 'h2' }, + { title: 'Header 3', format: 'h3' }, + { title: 'Header 4', format: 'h4' }, + { title: 'Header 5', format: 'h5' }, + { title: 'Header 6', format: 'h6' }, + { title: 'Pre', format: 'pre' }, + { title: 'Code', format: 'code' } + ] + }, + plain: { + ...DEFAULT_TINYMCE_CONFIG, + plugins: '', + toolbar: '' + }, + minimal: { + ...DEFAULT_TINYMCE_CONFIG, + plugins: 'link autolink', + toolbar: 'bold italic underline | link', + valid_elements: 'strong,em,span[style],a[href]' + } +}; diff --git a/core-web/libs/sdk/react/src/lib/mocks/mockPageContext.tsx b/core-web/libs/sdk/react/src/lib/mocks/mockPageContext.tsx index f00c83e03b1b..e6b1526de3af 100644 --- a/core-web/libs/sdk/react/src/lib/mocks/mockPageContext.tsx +++ b/core-web/libs/sdk/react/src/lib/mocks/mockPageContext.tsx @@ -1,5 +1,30 @@ import { PageProvider } from '../components/PageProvider/PageProvider'; -import { DotCMSPageContext } from '../models'; +import { DotCMSPageContext, DotCMSContentlet } from '../models'; + +export const dotcmsContentletMock: DotCMSContentlet = { + archived: false, + baseType: '', + contentType: '', + folder: '', + hasTitleImage: false, + host: '', + hostName: '', + identifier: '', + inode: '', + languageId: 1, + live: false, + locked: false, + modDate: '', + modUser: '', + modUserName: '', + owner: '', + sortOrder: 1, + stInode: '', + title: 'This is my editable title', + titleImage: '', + url: '', + working: false +}; export const mockPageContext: DotCMSPageContext = { pageAsset: { diff --git a/core-web/libs/sdk/react/src/lib/models/index.ts b/core-web/libs/sdk/react/src/lib/models/index.ts index 42fc4275b389..4378ffa8156a 100644 --- a/core-web/libs/sdk/react/src/lib/models/index.ts +++ b/core-web/libs/sdk/react/src/lib/models/index.ts @@ -87,3 +87,44 @@ export interface DotCMSPageContext { }; isInsideEditor: boolean; } + +export interface DotCMSContentlet { + archived: boolean; + binaryContentAsset?: string; + deleted?: boolean; + baseType: string; + binary?: string; + binaryVersion?: string; + file?: string; + contentType: string; + hasLiveVersion?: boolean; + folder: string; + hasTitleImage: boolean; + hostName: string; + host: string; + inode: string; + identifier: string; + languageId: number; + image?: string; + locked: boolean; + language?: string; + mimeType?: string; + modUser: string; + modDate: string; + live: boolean; + sortOrder: number; + owner: string; + title: string; + stInode: string; + titleImage: string; + modUserName: string; + text?: string; + working: boolean; + url: string; + contentTypeIcon?: string; + body?: string; + variant?: string; + __icon__?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; // This is a catch-all for any other custom properties that might be on the contentlet. +} diff --git a/core-web/package.json b/core-web/package.json index c69a8486aef6..a8cb54f8f502 100644 --- a/core-web/package.json +++ b/core-web/package.json @@ -74,6 +74,7 @@ "@nx/angular": "19.5.6", "@tarekraafat/autocomplete.js": "^10.2.6", "@tinymce/tinymce-angular": "^7.0.0", + "@tinymce/tinymce-react": "^5.1.1", "@tiptap/core": "^2.0.0-beta.218", "@tiptap/extension-bubble-menu": "^2.0.0-beta.218", "@tiptap/extension-character-count": "^2.0.0-beta.218", diff --git a/core-web/yarn.lock b/core-web/yarn.lock index 3b4d951f1b17..216f98717367 100644 --- a/core-web/yarn.lock +++ b/core-web/yarn.lock @@ -5177,6 +5177,14 @@ tinymce "^6.0.0 || ^5.5.0" tslib "^2.3.0" +"@tinymce/tinymce-react@^5.1.1": + version "5.1.1" + resolved "https://registry.npmjs.org/@tinymce/tinymce-react/-/tinymce-react-5.1.1.tgz#3b8555ceaccfa6bb8bb03c3b0c8baaccde138bde" + integrity sha512-DQ0wpvnf/9z8RsOEAmrWZ1DN1PKqcQHfU+DpM3llLze7FHmxVtzuN8O+FYh0oAAF4stzAXwiCIVacfqjMwRieQ== + dependencies: + prop-types "^15.6.2" + tinymce "^7.0.0 || ^6.0.0 || ^5.5.1" + "@tiptap/core@^2.0.0-beta.218", "@tiptap/core@^2.5.8": version "2.5.8" resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.5.8.tgz#58de366b0d2acb0a6e67a4780de64d619ebd90fa" @@ -18590,7 +18598,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -21206,6 +21214,11 @@ tiny-relative-date@^1.3.0: resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-6.8.4.tgz#53e1313ebfe5524b24c0fa45937d51fda058632d" integrity sha512-okoJyxuPv1gzASxQDNgQbnUXOdAIyoOSXcXcZZu7tiW0PSKEdf3SdASxPBupRj+64/E3elHwVRnzSdo82Emqbg== +"tinymce@^7.0.0 || ^6.0.0 || ^5.5.1": + version "7.3.0" + resolved "https://registry.npmjs.org/tinymce/-/tinymce-7.3.0.tgz#929b5d6d151b7d5e6f2efdaa802484834c42872c" + integrity sha512-Ls4PgYlpk73XAxBSBqbVmSl8Mb3DuNfgF01GZ0lY6/MOEVRl3IL+VxC1Oe6165e8WqbqVsxO3Qj/PmoYNvQKGQ== + tippy.js@^6.3.7: version "6.3.7" resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c" diff --git a/examples/nextjs/jsconfig.json b/examples/nextjs/jsconfig.json index 611be824c455..0d856efda589 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.json b/examples/nextjs/package.json index d24ace61533d..6e1663951154 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@tinymce/tinymce-react": "^5.1.1", "@dotcms/client": "0.0.1-alpha.32", "@dotcms/react": "0.0.1-alpha.32", "@dotcms/experiments": "0.0.1-alpha.32", diff --git a/examples/nextjs/src/components/content-types/banner.js b/examples/nextjs/src/components/content-types/banner.js index fa4ac476e5e3..b323ba96ff1c 100644 --- a/examples/nextjs/src/components/content-types/banner.js +++ b/examples/nextjs/src/components/content-types/banner.js @@ -1,7 +1,10 @@ import Image from "next/image"; import Link from "next/link"; +import { DotEditableText } from "@dotcms/react"; + +function Banner(contentlet) { + const { title, caption, image, link, buttonText } = contentlet; -function Banner({ title, image, caption, buttonText, link }) { return (
{image && ( @@ -13,7 +16,12 @@ function Banner({ title, image, caption, buttonText, link }) { /> )}
-

{title}

+

+ +

{caption}