diff --git a/admin/src/components/Input/components/CKEReact.jsx b/admin/src/components/Input/components/CKEReact.jsx new file mode 100644 index 0000000..cb02925 --- /dev/null +++ b/admin/src/components/Input/components/CKEReact.jsx @@ -0,0 +1,79 @@ +import React, { useRef, useState } from 'react'; +import { useField } from '@strapi/strapi/admin'; +import { Box } from '@strapi/design-system'; +import styled from 'styled-components'; +import { ClassicEditor } from 'ckeditor5'; +import { CKEditor } from '@ckeditor/ckeditor5-react'; +import 'ckeditor5/ckeditor5.css'; + +export const CKEReact = ({ + name, + disabled, + maxLength, + preset, + setEditorInstance, +}) => { + const { onChange: fieldOnChange, value, error } = useField(name); + + const [lengthMax, setLengthMax] = useState(false); + + const wordCounter = useRef(null); + + const handleCounter = (number) => setLengthMax(number > maxLength); + + const hasWordCountPlugin = Boolean(preset.editorConfig.WordCountPlugin); + + const onEditorReady = (editor) => { + if (hasWordCountPlugin) { + const wordCountPlugin = editor.plugins.get('WordCount'); + wordCountPlugin.on('update', (evt, stats) => + handleCounter(stats.characters) + ); + const wordCountWrapper = wordCounter.current; + wordCountWrapper?.appendChild(wordCountPlugin.wordCountContainer); + } + + if (editor.plugins.has('ImageUploadEditing')) { + editor.plugins + .get('ImageUploadEditing') + .on('uploadComplete', (evt, { data, imageElement }) => + editor.model.change((writer) => + writer.setAttribute('alt', data.alt, imageElement) + ) + ); + } + + setEditorInstance(editor); + }; + + const onEditorChange = (event, editor) => { + const data = editor.getData(); + fieldOnChange({ target: { name, value: data } }); + }; + + return ( + <> + + {hasWordCountPlugin && ( + + )} + + ); +}; + +const WordCounterBox = styled(Box)` + display: flex; + width: 100%; + justify-content: flex-end; + align-items: center; +`; diff --git a/admin/src/components/Input/components/Editor.jsx b/admin/src/components/Input/components/Editor.jsx index 5694727..1a1643a 100644 --- a/admin/src/components/Input/components/Editor.jsx +++ b/admin/src/components/Input/components/Editor.jsx @@ -1,20 +1,14 @@ -import React, { useEffect, useRef, useState } from 'react'; -import PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; +import { Loader } from '@strapi/design-system'; +import { Box } from '@strapi/design-system'; import styled from 'styled-components'; -import { CKEditor } from '@ckeditor/ckeditor5-react'; -import { ClassicEditor } from 'ckeditor5'; -import { Box, Loader } from '@strapi/design-system'; -import { useField } from '@strapi/strapi/admin'; -import 'ckeditor5/ckeditor5.css'; +import { CKEReact } from './CKEReact'; +import { EditorLayout } from './EditorLayout'; import { MediaLib } from './MediaLib'; import { getConfiguredPreset } from '../config'; import { GlobalStyling } from './GlobalStyling'; -const Wrapper = styled('div')` - ${({ styles }) => styles} -`; - export const Editor = ({ name, disabled, @@ -22,27 +16,24 @@ export const Editor = ({ maxLength, placeholder, }) => { - const { onChange, value } = useField(name); - - const [editorInstance, setEditorInstance] = useState(false); - const [mediaLibVisible, setMediaLibVisible] = useState(false); - + const [editorInstance, setEditorInstance] = useState(false); const [preset, setPreset] = useState(null); - const [lengthMax, setLengthMax] = useState(false); - - const wordCounter = useRef(null); + const toggleMediaLib = () => setMediaLibVisible((prev) => !prev); - const handleToggleMediaLib = () => setMediaLibVisible((prev) => !prev); + const handleChangeAssets = (newElems) => { + const viewFragment = editorInstance.data.processor.toView(newElems); + const modelFragment = editorInstance.data.toModel(viewFragment); + editorInstance.model.insertContent(modelFragment); - const handleCounter = (number) => - number > maxLength ? setLengthMax(true) : setLengthMax(false); + toggleMediaLib(); + }; useEffect(() => { (async () => { const currentPreset = await getConfiguredPreset(presetName, { - toggleMediaLib: handleToggleMediaLib, + toggleMediaLib: toggleMediaLib, strapiFieldPlaceholder: placeholder, }); @@ -50,89 +41,39 @@ export const Editor = ({ })(); }, []); + if (!preset) { + return ( + + Loading... + + ); + } + return ( <> - {preset && } - - {!preset && ( - - Loading... - - )} - {preset && ( - <> - { - if (preset.editorConfig.WordCountPlugin) { - const wordCountPlugin = editor.plugins.get('WordCount'); - wordCountPlugin.on('update', (evt, stats) => - handleCounter(stats.characters) - ); - const wordCountWrapper = wordCounter.current; - wordCountWrapper?.appendChild( - wordCountPlugin.wordCountContainer - ); - } - - if (editor.plugins.has('ImageUploadEditing')) { - editor.plugins - .get('ImageUploadEditing') - .on('uploadComplete', (evt, { data, imageElement }) => - editor.model.change((writer) => - writer.setAttribute('alt', data.alt, imageElement) - ) - ); - } - - setEditorInstance(editor); - }} - onChange={(event, editor) => { - const data = editor.getData(); - onChange({ target: { name, value: data } }); - }} - /> - - {preset.editorConfig.WordCountPlugin && ( - - {!editorInstance && Loading...} - - )} - - )} - + + + + + ); }; -Editor.propTypes = { - name: PropTypes.string.isRequired, - disabled: PropTypes.bool, - presetName: PropTypes.string.isRequired, - maxLength: PropTypes.number, - placeholder: PropTypes.string, -}; - -const CounterLoaderBox = styled(Box)` - display: flex; - width: 100%; - justify-content: flex-end; - align-items: center; -`; const LoaderBox = styled(Box)` display: flex; - height: 200px; - width: 100%; justify-content: center; align-items: center; -`; + height: 200px; + width: 100%; +`; \ No newline at end of file diff --git a/admin/src/components/Input/components/EditorLayout.jsx b/admin/src/components/Input/components/EditorLayout.jsx new file mode 100644 index 0000000..6e1d97e --- /dev/null +++ b/admin/src/components/Input/components/EditorLayout.jsx @@ -0,0 +1,106 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Flex, IconButton, FocusTrap, Portal } from '@strapi/design-system'; +import { Expand, Collapse } from '@strapi/icons'; +import styled from 'styled-components'; + +export const EditorLayout = ({ children, presetStyles }) => { + const [isExpandedMode, setIsExpandedMode] = useState(false); + + const handleToggleExpand = () => setIsExpandedMode(true); + const handleOnCollapse = () => setIsExpandedMode(false); + + useEffect(() => { + if (isExpandedMode) { + document.body.classList.add('lock-body-scroll'); + } + + return () => { + document.body.classList.remove('lock-body-scroll'); + }; + }, [isExpandedMode]); + + if (isExpandedMode) { + return ( + + + + e.stopPropagation()} + position="relative" + > + + + {children} + + + + + + + + + + ); + } + + return ( + + {children} + + + + + ); +}; + +const EditorWrapper = styled('div')` + position: relative; + width: 100%; + height: 100%; + + ${({ $presetStyles }) => $presetStyles} +`; + +const Backdrop = styled(Flex)` + // Background with 20% opacity + background: ${({ theme }) => `${theme.colors.neutral800}1F`}; +`; + +const ExpandButton = styled(IconButton)` + position: absolute; + bottom: 1.4rem; + right: 1.2rem; + z-index: 2; +`; + +const CollapseButton = styled(IconButton)` + position: absolute; + bottom: 2.5rem; + right: 1.2rem; + z-index: 2; +`; + +const FullScreenBox = styled(Box)` + max-width: var(--ck-editor-full-screen-box-max-width); +` \ No newline at end of file diff --git a/admin/src/components/Input/components/MediaLib.jsx b/admin/src/components/Input/components/MediaLib.jsx index 5eb4e05..2eab61f 100644 --- a/admin/src/components/Input/components/MediaLib.jsx +++ b/admin/src/components/Input/components/MediaLib.jsx @@ -1,16 +1,15 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { useStrapiApp } from '@strapi/strapi/admin'; import { prefixFileUrlWithBackendUrl } from '../../../utils/prefixFileUrlWithBackendUrl' -export const MediaLib = ({ isOpen = false, onToggle = () => {}, editor }) => { +export const MediaLib = ({ isOpen = false, toggle, handleChangeAssets }) => { const components = useStrapiApp('MediaLib', ({ components }) => components); const MediaLibraryDialog = components['media-library']; - const handleChangeAssets = (assets) => { - let newValue = ''; + const getNewElems = (assets) => { + let newElems = ''; assets.map(({ name, url, alt, formats, mime, width, height }) => { if (mime.includes('image')) { @@ -18,25 +17,21 @@ export const MediaLib = ({ isOpen = false, onToggle = () => {}, editor }) => { let set = ''; let keys = Object.keys(formats).sort((a, b) => formats[a].width - formats[b].width); keys.map((k) => (set += prefixFileUrlWithBackendUrl(formats[k].url) +` ${formats[k].width}w,`)); - newValue += `${alt}`; + newElems += `${alt}`; } else { - newValue += `${alt}`; + newElems += `${alt}`; } } else if (mime.includes('video')) { - newValue += ` + newElems += ` `; } else { - newValue += `${name || 'Open document'}`; + newElems += `${name || 'Open document'}`; } }); - const viewFragment = editor.data.processor.toView(newValue); - const modelFragment = editor.data.toModel(viewFragment); - editor.model.insertContent(modelFragment); - - onToggle(); + return newElems; }; const handleSelectAssets = (files) => { @@ -50,7 +45,9 @@ export const MediaLib = ({ isOpen = false, onToggle = () => {}, editor }) => { height: f.height, })); - handleChangeAssets(formattedFiles); + const newElems = getNewElems(formattedFiles); + + handleChangeAssets(newElems); }; if (!isOpen) { @@ -59,13 +56,8 @@ export const MediaLib = ({ isOpen = false, onToggle = () => {}, editor }) => { return ( ); -}; - -MediaLib.propTypes = { - isOpen: PropTypes.bool, - onToggle: PropTypes.func, }; \ No newline at end of file diff --git a/admin/src/components/Input/index.jsx b/admin/src/components/Input/index.jsx index 85e545b..befd913 100644 --- a/admin/src/components/Input/index.jsx +++ b/admin/src/components/Input/index.jsx @@ -1,28 +1,28 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Field, Flex } from '@strapi/design-system'; +import { useField } from '@strapi/admin/strapi-admin'; import { Editor } from './components/Editor'; const Input = ({ name, attribute, - value = '', labelAction = null, label, disabled = false, - error = null, required = false, - hint = '', + hint, placeholder, }) => { const { preset, maxLengthCharacters, ...options } = attribute.options; + const field = useField(name); return ( @@ -31,7 +31,6 @@ const Input = ({