From 17e1d10a2de1c8ba4d12a7364bc8858761b4b92e Mon Sep 17 00:00:00 2001 From: Piero Nicolli Date: Thu, 14 Dec 2023 12:45:31 +0100 Subject: [PATCH 01/26] fix: custom inline styles rendering in wysiwygwidget --- RELEASE.md | 16 +- src/config/RichTextEditor/config.js | 43 ++- .../manage/Widgets/WysiwygWidget.jsx | 362 ++++++++++++++++++ 3 files changed, 407 insertions(+), 14 deletions(-) create mode 100644 src/customizations/volto/components/manage/Widgets/WysiwygWidget.jsx diff --git a/RELEASE.md b/RELEASE.md index cc02cb738..ff67a44db 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -41,11 +41,25 @@ - ... --> +## Versione X.X.X (dd/mm/yyyy) + +### Migliorie + +- ... + +### Novità + +- ... [`Istruzioni`](url della documentazione relativa alla novità) + +### Fix + +- Ora si vedono correttamente gli stili di allineamento del testo in alcuni editor di testo, ad esempio header e footer dei sottositi. + ## Versione 10.6.1 (13/12/2023) ### Novità -- I seguenti campi sono ora riordinabili liberamente: "Timeline tempi e scadenze" per il tipo di contenuto *Servizio* e "Valore punto di contatto" del tipo di contenuto *Punto di contatto*. +- I seguenti campi sono ora riordinabili liberamente: "Timeline tempi e scadenze" per il tipo di contenuto _Servizio_ e "Valore punto di contatto" del tipo di contenuto _Punto di contatto_. ### Fix diff --git a/src/config/RichTextEditor/config.js b/src/config/RichTextEditor/config.js index 01384f3be..5c5558610 100644 --- a/src/config/RichTextEditor/config.js +++ b/src/config/RichTextEditor/config.js @@ -136,33 +136,50 @@ const ItaliaFromHTMLCustomBlockFn = (element) => { ret = { type: 'buttons', }; - } else if (element.className === 'draftjs-text-larger') { + } else if (element.className === 'text-center') { ret = { - type: 'TEXT_LARGER', + type: 'align-center', + }; + } else if (element.className === 'text-end') { + ret = { + type: 'align-right', + }; + } else if (element.className === 'text-justify') { + ret = { + type: 'align-justify', }; } } return ret; }; +const ItaliaFromHTMLCustomInlineFn = (element, { Style }) => { + if (element.tagName === 'SPAN') { + if (element.className === 'draftjs-text-larger') { + return Style('TEXT_LARGER'); + } + } +}; + export default function applyConfig(config) { config.settings.richtextEditorSettings = (props) => { const { plugins /*, inlineToolbarButtons*/ } = Plugins(props); // volto plugins - const { extendedBlockRenderMap, blockStyleFn, listBlockTypes } = - Blocks(props); + const { extendedBlockRenderMap, blockStyleFn, listBlockTypes } = Blocks( + props, + ); const { immutableLib } = props; const { Map } = immutableLib; const blockRenderMap = Map({ 'align-center': { - element: 'p', + element: (props) =>

, }, 'align-right': { - element: 'p', + element: (props) =>

, }, 'align-justify': { - element: 'p', + element: (props) =>

, }, 'callout-bg': { element: 'p', @@ -179,9 +196,6 @@ export default function applyConfig(config) { r = r.length > 0 ? ' ' : r; const styles = { - 'align-center': 'text-center', - 'align-right': 'text-end', - 'align-justify': 'text-justify', callout: 'callout', 'callout-bg': 'callout-bg', buttons: 'draftjs-buttons', @@ -203,9 +217,12 @@ export default function applyConfig(config) { ...plugins, ...ItaliaRichTextEditorPlugins(props), ], - richTextEditorInlineToolbarButtons: - ItaliaRichTextEditorInlineToolbarButtons(props, plugins), //[inlineToolbarButtons,...ItaliaRichTextEditorInlineToolbarButtons(props)] - FromHTMLCustomBlockFn: ItaliaFromHTMLCustomBlockFn, //FromHTMLCustomBlockFn + richTextEditorInlineToolbarButtons: ItaliaRichTextEditorInlineToolbarButtons( + props, + plugins, + ), //[inlineToolbarButtons,...ItaliaRichTextEditorInlineToolbarButtons(props)] + FromHTMLCustomBlockFn: ItaliaFromHTMLCustomBlockFn, + FromHTMLCustomInlineFn: ItaliaFromHTMLCustomInlineFn, customStyleMap: { TEXT_LARGER: { fontSize: '1.75rem' }, }, diff --git a/src/customizations/volto/components/manage/Widgets/WysiwygWidget.jsx b/src/customizations/volto/components/manage/Widgets/WysiwygWidget.jsx new file mode 100644 index 000000000..b0239a747 --- /dev/null +++ b/src/customizations/volto/components/manage/Widgets/WysiwygWidget.jsx @@ -0,0 +1,362 @@ +/** + * Customizations + * - add customInlineFn to stateFromHTML call (line 179) + */ +/** + * WysiwygWidget container. + * @module components/manage/WysiwygWidget/WysiwygWidget + */ + +import React, { Component } from 'react'; +import ReactDOMServer from 'react-dom/server'; +import PropTypes from 'prop-types'; +import { connect, Provider } from 'react-redux'; +import { compose } from 'redux'; +import redraft from 'redraft'; +import { Form, Label, TextArea } from 'semantic-ui-react'; +import { map } from 'lodash'; +import { defineMessages, injectIntl } from 'react-intl'; +import configureStore from 'redux-mock-store'; +import { MemoryRouter } from 'react-router-dom'; +import config from '@plone/volto/registry'; + +import { FormFieldWrapper } from '@plone/volto/components'; + +import loadable from '@loadable/component'; +import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; + +const Editor = loadable(() => import('draft-js-plugins-editor')); + +const messages = defineMessages({ + default: { + id: 'Default', + defaultMessage: 'Default', + }, + idTitle: { + id: 'Short Name', + defaultMessage: 'Short Name', + }, + idDescription: { + id: 'Used for programmatic access to the fieldset.', + defaultMessage: 'Used for programmatic access to the fieldset.', + }, + title: { + id: 'Title', + defaultMessage: 'Title', + }, + description: { + id: 'Description', + defaultMessage: 'Description', + }, + required: { + id: 'Required', + defaultMessage: 'Required', + }, + delete: { + id: 'Delete', + defaultMessage: 'Delete', + }, +}); + +/** + * WysiwygWidget HTML richtext editing widget + * + * To use it, in schema properties, declare a field like: + * + * ```jsx + * { + * title: "Rich text", + * widget: 'richtext', + * } + * ``` + * + */ +class WysiwygWidgetComponent extends Component { + /** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ + static propTypes = { + /** + * Id of the field + */ + id: PropTypes.string.isRequired, + /** + * Title of the field + */ + title: PropTypes.string.isRequired, + /** + * Description of the field + */ + description: PropTypes.string, + /** + * True if field is required + */ + required: PropTypes.bool, + /** + * Value of the field + */ + value: PropTypes.shape({ + /** + * Content type of the value + */ + 'content-type': PropTypes.string, + /** + * Data of the value + */ + data: PropTypes.string, + /** + * Encoding of the value + */ + encoding: PropTypes.string, + }), + /** + * Placeholder for the editor + */ + placeholder: PropTypes.string, + /** + * List of error messages + */ + error: PropTypes.arrayOf(PropTypes.string), + /** + * On change handler + */ + onChange: PropTypes.func, + /** + * On delete handler + */ + onDelete: PropTypes.func, + /** + * On edit handler + */ + onEdit: PropTypes.func, + /** + * Wrapped form component + */ + wrapped: PropTypes.bool, + }; + + /** + * Default properties + * @property {Object} defaultProps Default properties. + * @static + */ + static defaultProps = { + description: null, + required: false, + value: { + 'content-type': 'text/html', + data: '', + encoding: 'utf8', + }, + error: [], + onEdit: null, + onDelete: null, + onChange: null, + }; + + /** + * Constructor + * @method constructor + * @param {Object} props Component properties + * @constructs WysiwygWidget + */ + constructor(props) { + super(props); + + const { stateFromHTML } = props.draftJsImportHtml; + const { EditorState } = props.draftJs; + const createInlineToolbarPlugin = props.draftJsInlineToolbarPlugin.default; + + this.draftConfig = config.settings.richtextEditorSettings(props); + + if (!__SERVER__) { + let editorState; + if (props.value && props.value.data) { + const contentState = stateFromHTML(props.value.data, { + customBlockFn: this.draftConfig.FromHTMLCustomBlockFn, + customInlineFn: this.draftConfig.FromHTMLCustomInlineFn, + }); + editorState = EditorState.createWithContent(contentState); + } else { + editorState = EditorState.createEmpty(); + } + + const inlineToolbarPlugin = createInlineToolbarPlugin({ + structure: this.draftConfig.richTextEditorInlineToolbarButtons, + }); + + this.state = { editorState, inlineToolbarPlugin }; + } + + this.schema = { + fieldsets: [ + { + id: 'default', + title: props.intl.formatMessage(messages.default), + fields: ['title', 'id', 'description', 'required'], + }, + ], + properties: { + id: { + type: 'string', + title: props.intl.formatMessage(messages.idTitle), + description: props.intl.formatMessage(messages.idDescription), + }, + title: { + type: 'string', + title: props.intl.formatMessage(messages.title), + }, + description: { + type: 'string', + widget: 'textarea', + title: props.intl.formatMessage(messages.description), + }, + required: { + type: 'boolean', + title: props.intl.formatMessage(messages.required), + }, + }, + required: ['id', 'title'], + }; + + this.onChange = this.onChange.bind(this); + } + + /** + * Change handler + * @method onChange + * @param {object} editorState Editor state. + * @returns {undefined} + */ + onChange(editorState) { + const { convertToRaw } = this.props.draftJs; + this.setState({ editorState }); + const mockStore = configureStore(); + + this.props.onChange(this.props.id, { + 'content-type': this.props.value + ? this.props.value['content-type'] + : 'text/html', + encoding: this.props.value ? this.props.value.encoding : 'utf8', + data: ReactDOMServer.renderToStaticMarkup( + + + {redraft( + convertToRaw(editorState.getCurrentContent()), + config.settings.richtextViewSettings.ToHTMLRenderers, + config.settings.richtextViewSettings.ToHTMLOptions, + )} + + , + ), + }); + } + + /** + * Render method. + * @method render + * @returns {string} Markup for the component. + */ + render() { + const { + id, + title, + description, + required, + value, + error, + fieldSet, + } = this.props; + + if (__SERVER__) { + return ( + 0} + className={description ? 'help' : ''} + id={`${fieldSet || 'field'}-${id}`} + > +

+ +