From 7281db191493b9e64ec16753101be9b1e766e98e Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Tue, 12 Nov 2024 17:05:21 -0400 Subject: [PATCH] chore(SDK React): bring Inline Editing for Block Editor --- core-web/libs/sdk/client/src/index.ts | 6 ++- .../sdk/client/src/lib/editor/sdk-editor.ts | 35 ++++++++++++++ .../BlockEditorRenderer.tsx | 25 +++++++++- .../webapp/html/js/editor-js/sdk-editor.js | 2 +- .../src/components/content-types/blog.js | 48 +++++++++++-------- 5 files changed, 93 insertions(+), 23 deletions(-) diff --git a/core-web/libs/sdk/client/src/index.ts b/core-web/libs/sdk/client/src/index.ts index d95a975f1375..aa553c8771d5 100644 --- a/core-web/libs/sdk/client/src/index.ts +++ b/core-web/libs/sdk/client/src/index.ts @@ -11,7 +11,8 @@ import { editContentlet, initEditor, isInsideEditor, - updateNavigation + updateNavigation, + initInlineEditing } from './lib/editor/sdk-editor'; import { getPageRequestParams, graphqlToPageEntity } from './lib/utils'; @@ -30,5 +31,6 @@ export { initEditor, updateNavigation, destroyEditor, - ClientConfig + ClientConfig, + initInlineEditing }; diff --git a/core-web/libs/sdk/client/src/lib/editor/sdk-editor.ts b/core-web/libs/sdk/client/src/lib/editor/sdk-editor.ts index 02480c0fbbdb..94c081f0a5b3 100644 --- a/core-web/libs/sdk/client/src/lib/editor/sdk-editor.ts +++ b/core-web/libs/sdk/client/src/lib/editor/sdk-editor.ts @@ -126,3 +126,38 @@ export function addClassToEmptyContentlets(): void { contentlet.classList.add('empty-contentlet'); }); } + +const INLINE_EDITING_EVENT: Record = { + blockEditor: CLIENT_ACTIONS.INIT_BLOCK_EDITOR_INLINE_EDITING +}; + +/** + * Initializes the block editor inline editing. + * + * @export + * @param {*} { inode, language_id, blockEditorContent } + */ +export function initInlineEditing( + type: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { inode, languageId, contentType, fieldName, content }: any // Any for now +) { + const action = INLINE_EDITING_EVENT[type]; + + if (!action) { + return; + } + + const contentString = typeof content === 'string' ? content : JSON.stringify(content ?? {}); + + postMessageToEditor({ + action, + payload: { + inode, + fieldName, + contentType, + languageId, + blockEditorContent: contentString + } + }); +} 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 index 200a34b1eb51..748f0824184a 100644 --- a/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/BlockEditorRenderer.tsx +++ b/core-web/libs/sdk/react/src/lib/components/BlockEditorRenderer/BlockEditorRenderer.tsx @@ -1,10 +1,18 @@ +import { useEffect, useRef } from 'react'; + +import { initInlineEditing } from '@dotcms/client'; + import { BlockEditorBlock } from './item/BlockEditorBlock'; +import { DotCMSContentlet } from '../../models'; import { Block } from '../../models/blocks.interface'; import { CustomRenderer } from '../../models/content-node.interface'; export interface BlockEditorRendererProps { blocks: Block; + editable?: boolean; + contentlet?: DotCMSContentlet; + fieldName?: string; customRenderers?: CustomRenderer; className?: string; style?: React.CSSProperties; @@ -23,12 +31,27 @@ export interface BlockEditorRendererProps { */ export const BlockEditorRenderer = ({ blocks, + editable, + contentlet, + fieldName, customRenderers, className, style }: BlockEditorRendererProps) => { + const ref = useRef(null); + + useEffect(() => { + if (!editable || !ref.current) { + return; + } + + ref.current?.addEventListener('click', () => { + initInlineEditing('blockEditor', { ...contentlet, fieldName, content: blocks.content }); + }); + }, [editable, contentlet, blocks.content, fieldName]); + return ( -
+
); diff --git a/dotCMS/src/main/webapp/html/js/editor-js/sdk-editor.js b/dotCMS/src/main/webapp/html/js/editor-js/sdk-editor.js index dc38315a4878..4edd0a4efc1c 100644 --- a/dotCMS/src/main/webapp/html/js/editor-js/sdk-editor.js +++ b/dotCMS/src/main/webapp/html/js/editor-js/sdk-editor.js @@ -1 +1 @@ -function u(t){i({action:"edit-contentlet",payload:t})}function T(){return typeof window>"u"?!1:window.parent!==window}function f(){window.dotUVE=a}function m(){document.querySelectorAll('[data-dot-object="contentlet"]').forEach(n=>{n.clientHeight||n.classList.add("empty-contentlet")})}var a={editContentlet:u,lastScrollYPosition:0};function i(t){window.parent.postMessage(t,"*")}function _(t){return t.map(n=>{let e=n.getBoundingClientRect(),o=Array.from(n.querySelectorAll('[data-dot-object="contentlet"]'));return{x:e.x,y:e.y,width:e.width,height:e.height,payload:JSON.stringify({container:v(n)}),contentlets:h(e,o)}})}function h(t,n){return n.map(e=>{let o=e.getBoundingClientRect();return{x:0,y:o.y-t.y,width:o.width,height:o.height,payload:JSON.stringify({container:e.dataset?.dotContainer?JSON.parse(e.dataset?.dotContainer):E(e),contentlet:{identifier:e.dataset?.dotIdentifier,title:e.dataset?.dotTitle,inode:e.dataset?.dotInode,contentType:e.dataset?.dotType}})}})}function v(t){return{acceptTypes:t.dataset?.dotAcceptTypes||"",identifier:t.dataset?.dotIdentifier||"",maxContentlets:t.dataset?.maxContentlets||"",uuid:t.dataset?.dotUuid||""}}function E(t){let n=t.closest('[data-dot-object="container"]');return n?v(n):(console.warn("No container found for the contentlet"),null)}function p(t){return t?t?.dataset?.dotObject==="contentlet"||t?.dataset?.dotObject==="container"&&t.children.length===0?t:p(t?.parentElement):null}function y(t){let n=t.querySelectorAll('[data-dot-object="vtl-file"]');return n.length?Array.from(n).map(e=>({inode:e.dataset?.dotInode,name:e.dataset?.dotUrl})):null}function D(){let t=document.documentElement.scrollHeight,n=window.innerHeight;return window.scrollY+n>=t}var r=[];function S(){let t=Array.from(document.querySelectorAll('[data-dot-object="container"]')),n=_(t);i({action:"set-bounds",payload:n})}function l(){let t=n=>{({"uve-reload-page":()=>{window.location.reload()},"uve-request-bounds":()=>{S()},"uve-scroll-inside-iframe":()=>{let o=n.data.direction;if(window.scrollY===0&&o==="up"||D()&&o==="down")return;let d=o==="up"?-120:120;window.scrollBy({left:0,top:d,behavior:"smooth"})}})[n.data.name]?.()};window.addEventListener("message",t),r.push({type:"listener",event:"message",callback:t})}function s(){let t=n=>{let e=p(n.target);if(!e)return;let{x:o,y:d,width:L,height:w}=e.getBoundingClientRect(),M=e.dataset?.dotObject==="container",N={identifier:"TEMP_EMPTY_CONTENTLET",title:"TEMP_EMPTY_CONTENTLET",contentType:"TEMP_EMPTY_CONTENTLET_TYPE",inode:"TEMPY_EMPTY_CONTENTLET_INODE",widgetTitle:"TEMP_EMPTY_CONTENTLET",baseType:"TEMP_EMPTY_CONTENTLET",onNumberOfPages:1},P={identifier:e.dataset?.dotIdentifier,title:e.dataset?.dotTitle,inode:e.dataset?.dotInode,contentType:e.dataset?.dotType,baseType:e.dataset?.dotBasetype,widgetTitle:e.dataset?.dotWidgetTitle,onNumberOfPages:e.dataset?.dotOnNumberOfPages},O=y(e),b={container:e.dataset?.dotContainer?JSON.parse(e.dataset?.dotContainer):E(e),contentlet:M?N:P,vtlFiles:O};i({action:"set-contentlet",payload:{x:o,y:d,width:L,height:w,payload:b}})};document.addEventListener("pointermove",t),r.push({type:"listener",event:"pointermove",callback:t})}function c(){let t=()=>{i({action:"scroll"}),window.dotUVE={...window.dotUVE??a,lastScrollYPosition:window.scrollY}},n=()=>{i({action:"scroll-end"})};window.addEventListener("scroll",t),window.addEventListener("scrollend",n),r.push({type:"listener",event:"scroll",callback:n}),r.push({type:"listener",event:"scroll",callback:t})}function C(){let t=()=>{window.scrollTo(0,window.dotUVE?.lastScrollYPosition)};window.addEventListener("load",t),r.push({type:"listener",event:"scroll",callback:t})}var I=()=>{let t=()=>{let n=document.querySelectorAll("[data-block-editor-content]");n.length&&n.forEach(e=>{e.classList.add("dotcms__inline-edit-field"),e.addEventListener("click",()=>{let o={...e.dataset};window.parent.postMessage({payload:o,action:"init-editor-inline-editing"},"*")})})};document.readyState==="complete"?t():window.addEventListener("load",()=>t())};T()&&(f(),l(),c(),C(),s(),m(),I()); +function u(t){i({action:"edit-contentlet",payload:t})}function T(){return typeof window>"u"?!1:window.parent!==window}function f(){window.dotUVE=a}function m(){document.querySelectorAll('[data-dot-object="contentlet"]').forEach(n=>{n.clientHeight||n.classList.add("empty-contentlet")})}var A={blockEditor:"init-editor-inline-editing"};var a={editContentlet:u,lastScrollYPosition:0};function i(t){window.parent.postMessage(t,"*")}function _(t){return t.map(n=>{let e=n.getBoundingClientRect(),o=Array.from(n.querySelectorAll('[data-dot-object="contentlet"]'));return{x:e.x,y:e.y,width:e.width,height:e.height,payload:JSON.stringify({container:I(n)}),contentlets:S(e,o)}})}function S(t,n){return n.map(e=>{let o=e.getBoundingClientRect();return{x:0,y:o.y-t.y,width:o.width,height:o.height,payload:JSON.stringify({container:e.dataset?.dotContainer?JSON.parse(e.dataset?.dotContainer):E(e),contentlet:{identifier:e.dataset?.dotIdentifier,title:e.dataset?.dotTitle,inode:e.dataset?.dotInode,contentType:e.dataset?.dotType}})}})}function I(t){return{acceptTypes:t.dataset?.dotAcceptTypes||"",identifier:t.dataset?.dotIdentifier||"",maxContentlets:t.dataset?.maxContentlets||"",uuid:t.dataset?.dotUuid||""}}function E(t){let n=t.closest('[data-dot-object="container"]');return n?I(n):(console.warn("No container found for the contentlet"),null)}function p(t){return t?t?.dataset?.dotObject==="contentlet"||t?.dataset?.dotObject==="container"&&t.children.length===0?t:p(t?.parentElement):null}function y(t){let n=t.querySelectorAll('[data-dot-object="vtl-file"]');return n.length?Array.from(n).map(e=>({inode:e.dataset?.dotInode,name:e.dataset?.dotUrl})):null}function v(){let t=document.documentElement.scrollHeight,n=window.innerHeight;return window.scrollY+n>=t}var r=[];function h(){let t=Array.from(document.querySelectorAll('[data-dot-object="container"]')),n=_(t);i({action:"set-bounds",payload:n})}function d(){let t=n=>{({"uve-reload-page":()=>{window.location.reload()},"uve-request-bounds":()=>{h()},"uve-scroll-inside-iframe":()=>{let o=n.data.direction;if(window.scrollY===0&&o==="up"||v()&&o==="down")return;let s=o==="up"?-120:120;window.scrollBy({left:0,top:s,behavior:"smooth"})}})[n.data.name]?.()};window.addEventListener("message",t),r.push({type:"listener",event:"message",callback:t})}function l(){let t=n=>{let e=p(n.target);if(!e)return;let{x:o,y:s,width:C,height:L}=e.getBoundingClientRect(),w=e.dataset?.dotObject==="container",M={identifier:"TEMP_EMPTY_CONTENTLET",title:"TEMP_EMPTY_CONTENTLET",contentType:"TEMP_EMPTY_CONTENTLET_TYPE",inode:"TEMPY_EMPTY_CONTENTLET_INODE",widgetTitle:"TEMP_EMPTY_CONTENTLET",baseType:"TEMP_EMPTY_CONTENTLET",onNumberOfPages:1},O={identifier:e.dataset?.dotIdentifier,title:e.dataset?.dotTitle,inode:e.dataset?.dotInode,contentType:e.dataset?.dotType,baseType:e.dataset?.dotBasetype,widgetTitle:e.dataset?.dotWidgetTitle,onNumberOfPages:e.dataset?.dotOnNumberOfPages},b=y(e),P={container:e.dataset?.dotContainer?JSON.parse(e.dataset?.dotContainer):E(e),contentlet:w?M:O,vtlFiles:b};i({action:"set-contentlet",payload:{x:o,y:s,width:C,height:L,payload:P}})};document.addEventListener("pointermove",t),r.push({type:"listener",event:"pointermove",callback:t})}function c(){let t=()=>{i({action:"scroll"}),window.dotUVE={...window.dotUVE??a,lastScrollYPosition:window.scrollY}},n=()=>{i({action:"scroll-end"})};window.addEventListener("scroll",t),window.addEventListener("scrollend",n),r.push({type:"listener",event:"scroll",callback:n}),r.push({type:"listener",event:"scroll",callback:t})}function N(){let t=()=>{window.scrollTo(0,window.dotUVE?.lastScrollYPosition)};window.addEventListener("load",t),r.push({type:"listener",event:"scroll",callback:t})}var D=()=>{let t=()=>{let n=document.querySelectorAll("[data-block-editor-content]");n.length&&n.forEach(e=>{e.classList.add("dotcms__inline-edit-field"),e.addEventListener("click",()=>{let o={...e.dataset};window.parent.postMessage({payload:o,action:"init-editor-inline-editing"},"*")})})};document.readyState==="complete"?t():window.addEventListener("load",()=>t())};T()&&(f(),d(),c(),N(),l(),m(),D()); diff --git a/examples/nextjs/src/components/content-types/blog.js b/examples/nextjs/src/components/content-types/blog.js index 94cd82317faa..87b68bf40e4a 100644 --- a/examples/nextjs/src/components/content-types/blog.js +++ b/examples/nextjs/src/components/content-types/blog.js @@ -5,28 +5,38 @@ const CustomParagraph = ({ content }) => { return null; } const [{ text }] = content; - return

{text}

-} + return

{text}

; +}; const ActivityBlock = (props) => { const { title, description } = props.attrs.data; - return (
-

{title}

-

{description}

-
) -} - + return ( +
+

{title}

+

{description}

+
+ ); +}; -function BlogWithBlockEditor({blockEditorItem}){ - return +function Blog({ blockEditorItem, ...contentlet }) { + return ( + + ); } -export default BlogWithBlockEditor; \ No newline at end of file +export default Blog;