From c2076176f07fc75c21d481e7e53ffbce936647cf Mon Sep 17 00:00:00 2001 From: Giulia Ghisini Date: Fri, 22 Sep 2023 15:11:26 +0200 Subject: [PATCH 001/353] BREAKING CHANGE: use slate editor --- src/config/Widgets/widgets.js | 2 +- src/config/italiaConfig.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/config/Widgets/widgets.js b/src/config/Widgets/widgets.js index 43bdcc925..ccb04c4d9 100644 --- a/src/config/Widgets/widgets.js +++ b/src/config/Widgets/widgets.js @@ -82,7 +82,7 @@ const getItaliaWidgets = (config) => { }, widget: { ...config.widgets.widget, - richtext: WysiwygWidget, + //richtext: WysiwygWidget, color_list: ColorListWidget, path_filters: PathFiltersWidget, location_filter: LocationFiltersWidget, diff --git a/src/config/italiaConfig.js b/src/config/italiaConfig.js index a3ebafa65..4d67d298d 100644 --- a/src/config/italiaConfig.js +++ b/src/config/italiaConfig.js @@ -180,7 +180,7 @@ export default function applyConfig(voltoConfig) { 'social-settings': shareSVG, 'release-log': logSVG, }, - defaultBlockType: 'text', + //defaultBlockType: 'text', defaultExcludedFromSearch: { portalTypes: ['Image', 'File'], }, @@ -402,14 +402,14 @@ export default function applyConfig(voltoConfig) { ...config.blocks.blocksConfig.slate, restricted: true, }, - table: { - ...config.blocks.blocksConfig.table, - restricted: false, - }, - slateTable: { - ...config.blocks.blocksConfig.slateTable, - restricted: true, - }, + // table: { + // ...config.blocks.blocksConfig.table, + // restricted: false, + // }, + // slateTable: { + // ...config.blocks.blocksConfig.slateTable, + // restricted: true, + // }, maps: { ...config.blocks.blocksConfig.maps, restricted: true, From d4e4a71dc380166c71edfdc28384e8f1e75e6eb2 Mon Sep 17 00:00:00 2001 From: Giulia Ghisini Date: Fri, 22 Sep 2023 15:11:57 +0200 Subject: [PATCH 002/353] fix: fix font-style when adding new page --- src/theme/_cms-ui.scss | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/theme/_cms-ui.scss b/src/theme/_cms-ui.scss index 167967a92..9e5698382 100644 --- a/src/theme/_cms-ui.scss +++ b/src/theme/_cms-ui.scss @@ -84,12 +84,10 @@ body.cms-ui { &.contenttype-argomento, &.contenttype-pagina-argomento, &.contenttype-cartellamodulistica { - &:not(.section-add) { - .block { - font-family: $font-family-sans-serif; - font-weight: 300; - line-height: 1.555; - } + .block { + font-family: $font-family-sans-serif; + font-weight: 300; + line-height: 1.555; } } From 5018988eea0e7440a48fdc8321a4e47fce642eae Mon Sep 17 00:00:00 2001 From: Giulia Ghisini Date: Tue, 26 Sep 2023 10:12:16 +0200 Subject: [PATCH 003/353] chore: slate config --- src/config/Slate/Alignment/AlignMenu.jsx | 141 +++++++++++++++++++ src/config/Slate/Alignment/index.js | 23 ++++ src/config/Slate/Alignment/utils.js | 166 +++++++++++++++++++++++ src/config/Slate/config.js | 6 + src/config/Slate/dropdownStyle.scss | 9 ++ src/config/italiaConfig.js | 4 +- 6 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 src/config/Slate/Alignment/AlignMenu.jsx create mode 100644 src/config/Slate/Alignment/index.js create mode 100644 src/config/Slate/Alignment/utils.js create mode 100644 src/config/Slate/config.js create mode 100644 src/config/Slate/dropdownStyle.scss diff --git a/src/config/Slate/Alignment/AlignMenu.jsx b/src/config/Slate/Alignment/AlignMenu.jsx new file mode 100644 index 000000000..d7307ca4f --- /dev/null +++ b/src/config/Slate/Alignment/AlignMenu.jsx @@ -0,0 +1,141 @@ +import React from 'react'; +import { useSlate } from 'slate-react'; +import { Dropdown } from 'semantic-ui-react'; +import { useIntl, defineMessages } from 'react-intl'; +import cx from 'classnames'; +import { omit } from 'lodash'; +import { isBlockStyleActive, toggleStyle } from './utils'; + +import { Icon } from '@plone/volto/components'; +import { ToolbarButton } from '@plone/volto-slate/editor/ui'; + +import alignCenterSVG from '@plone/volto/icons/align-center.svg'; +import alignLeftSVG from '@plone/volto/icons/align-left.svg'; +import alignRightSVG from '@plone/volto/icons/align-right.svg'; +import alignJustifySVG from '@plone/volto/icons/align-justify.svg'; + +import '../dropdownStyle.scss'; + +const ALIGN_OPTIONS = [ + { + cssClass: 'text-left', + text: 'Allinea a sinistra', + icon: alignLeftSVG, + }, + { + cssClass: 'text-center', + text: 'Allinea al centro', + icon: alignCenterSVG, + }, + { + cssClass: 'text-right', + text: 'Allinea a destra', + icon: alignRightSVG, + }, + { + cssClass: 'text-justify', + text: 'Allinea il testo giustificato', + icon: alignJustifySVG, + }, +]; + +const messages = defineMessages({ + align: { + id: 'Allineamento', + defaultMessage: 'Allineamento', + }, +}); + +const AlignMenuButton = ({ icon, active, ...props }) => ( + +); + +const MenuOpts = ({ editor, toSelect, option, type }) => { + const isActive = toSelect.includes(option); + + return ( + { + toggleStyle(editor, { + cssClass: selItem.value, + isBlock: selItem.isBlock, + oneOfType: 'align', + }); + }} + /> + ); +}; + +const AlignButton = (props) => { + const editor = useSlate(); + const intl = useIntl(); + + const blockOpts = ALIGN_OPTIONS.map((def) => { + return { + value: def.cssClass, + text: def.label, + icon: (props) => , + isBlock: true, + originalIcon: def.icon, + }; + }); + + // Calculating the initial selection. + const toSelect = []; + let selectedIcon = ALIGN_OPTIONS[0].icon; + + // block styles + for (const val of blockOpts) { + const ia = isBlockStyleActive(editor, val.value); + + if (ia) { + toSelect.push(val); + selectedIcon = val.originalIcon; + } + } + + const menuItemProps = { + toSelect, + editor, + }; + + const showMenu = blockOpts.length; + + return showMenu ? ( + 0} + /> + } + > + + {blockOpts.length && + blockOpts.map((option, index) => ( + + ))} + + + ) : ( + '' + ); +}; + +export default AlignButton; diff --git a/src/config/Slate/Alignment/index.js b/src/config/Slate/Alignment/index.js new file mode 100644 index 000000000..9606f2ee0 --- /dev/null +++ b/src/config/Slate/Alignment/index.js @@ -0,0 +1,23 @@ +import React from 'react'; +import AlignMenu from './AlignMenu'; +//import '@plone/volto-slate/editor/plugins/StyleMenu/style.less'; + +export const AlignElement = ({ attributes, children, element }) => { + console.log(attributes, element); + return

aaaa{children}

; +}; + +export default function install(config) { + const { slate } = config.settings; + + slate.buttons.align = (props) => ( + + ); + + slate.elements['align'] = AlignElement; + slate.toolbarButtons.push('align'); + slate.expandedToolbarButtons.push('align'); + console.log(slate); + + return config; +} diff --git a/src/config/Slate/Alignment/utils.js b/src/config/Slate/Alignment/utils.js new file mode 100644 index 000000000..ce42c5aab --- /dev/null +++ b/src/config/Slate/Alignment/utils.js @@ -0,0 +1,166 @@ +/* eslint no-console: ["error", { allow: ["warn", "error"] }] */ +import { Editor, Transforms } from 'slate'; +import { isBlockActive } from '@plone/volto-slate/utils'; +import config from '@plone/volto/registry'; + +/** + * Toggles a style (e.g. in the StyleMenu plugin). + * @param {Editor} editor + * @param {object} options + * @param {boolean} options.oneOfType Whether the given type is admitted once + */ +export const toggleStyle = (editor, { cssClass, isBlock, oneOfType }) => { + if (isBlock) { + toggleBlockStyle(editor, cssClass, oneOfType); + } else { + toggleInlineStyle(editor, cssClass); + } +}; + +export const toggleBlockStyle = (editor, style, oneOfType) => { + // We have 6 boolean variables which need to be accounted for. + // See https://docs.google.com/spreadsheets/d/1mVeMuqSTMABV2BhoHPrPAFjn7zUksbNgZ9AQK_dcd3U/edit?usp=sharing + const { slate } = config.settings; + + const isListItem = isBlockActive(editor, slate.listItemType); + const isActive = isBlockStyleActive(editor, style); + const wantsList = false; + + if (isListItem && !wantsList) { + toggleBlockStyleAsListItem(editor, style); + } else if (isListItem && wantsList && !isActive) { + // switchListType(editor, format); // this will deconstruct to Volto blocks + } else if (!isListItem && wantsList) { + // changeBlockToList(editor, format); + } else if (!isListItem && !wantsList) { + internalToggleBlockStyle(editor, style); + } else { + console.warn('toggleBlockStyle case not covered, please examine:', { + wantsList, + isActive, + isListItem, + }); + } +}; + +export const toggleInlineStyle = (editor, style) => { + // We have 6 boolean variables which need to be accounted for. + // See https://docs.google.com/spreadsheets/d/1mVeMuqSTMABV2BhoHPrPAFjn7zUksbNgZ9AQK_dcd3U/edit?usp=sharing + const { slate } = config.settings; + + const isListItem = isBlockActive(editor, slate.listItemType); + const isActive = isInlineStyleActive(editor, style); + const wantsList = false; + + if (isListItem && !wantsList) { + toggleInlineStyleAsListItem(editor, style); + } else if (isListItem && wantsList && !isActive) { + // switchListType(editor, format); // this will deconstruct to Volto blocks + } else if (!isListItem && wantsList) { + // changeBlockToList(editor, format); + } else if (!isListItem && !wantsList) { + internalToggleInlineStyle(editor, style); + } else { + console.warn('toggleInlineStyle case not covered, please examine:', { + wantsList, + isActive, + isListItem, + }); + } +}; + +export const isBlockStyleActive = (editor, style) => { + const keyName = `style-${style}`; + const sn = Array.from( + Editor.nodes(editor, { + match: (n) => { + const isStyle = typeof n.styleName === 'string' || n[keyName]; + return !Editor.isEditor(n) && isStyle; + }, + mode: 'all', + }), + ); + + for (const [n] of sn) { + if (typeof n.styleName === 'string') { + if (n.styleName.split(' ').filter((x) => x === style).length > 0) { + return true; + } + } else if ( + n[keyName] && + keyName.split('style-').filter((x) => x === style).length > 0 + ) + return true; + } + return false; +}; + +export const isInlineStyleActive = (editor, style) => { + const m = Editor.marks(editor); + const keyName = `style-${style}`; + if (m && m[keyName]) { + return true; + } + return false; +}; + +export const internalToggleBlockStyle = (editor, style) => { + toggleBlockStyleInSelection(editor, style); +}; + +export const internalToggleInlineStyle = (editor, style) => { + toggleInlineStyleInSelection(editor, style); +}; + +/* + * Applies a block format unto a list item. Will split the list and deconstruct the + * block + */ +export const toggleBlockStyleAsListItem = (editor, style) => { + toggleBlockStyleInSelection(editor, style); +}; + +/* + * Applies an inline style unto a list item. + */ +export const toggleInlineStyleAsListItem = (editor, style) => { + toggleInlineStyleInSelection(editor, style); +}; + +function toggleInlineStyleInSelection(editor, style) { + const m = Editor.marks(editor); + const keyName = 'style-' + style; + + if (m && m[keyName]) { + Editor.removeMark(editor, keyName); + } else { + Editor.addMark(editor, keyName, true); + } +} + +function toggleBlockStyleInSelection(editor, style) { + const sn = Array.from( + Editor.nodes(editor, { + mode: 'highest', + match: (n) => { + return !Editor.isEditor(n); + }, + }), + ); + + for (const [n, p] of sn) { + let cn = n.styleName; + if (typeof n.styleName !== 'string') { + cn = style; + } else if (n.styleName.split(' ').filter((x) => x === style).length > 0) { + cn = cn + .split(' ') + .filter((x) => x !== style) + .join(' '); + } else { + // the style is not set but other styles are set + cn = cn.split(' ').concat(style).join(' '); + } + Transforms.setNodes(editor, { styleName: cn }, { at: p }); + } +} diff --git a/src/config/Slate/config.js b/src/config/Slate/config.js new file mode 100644 index 000000000..b55fb2283 --- /dev/null +++ b/src/config/Slate/config.js @@ -0,0 +1,6 @@ +//config.settings.slate.contextToolbarButtons +import installAlignment from 'design-comuni-plone-theme/config/Slate/Alignment'; + +export default function applyItaliaSlateConfig(config) { + installAlignment(config); +} diff --git a/src/config/Slate/dropdownStyle.scss b/src/config/Slate/dropdownStyle.scss new file mode 100644 index 000000000..fb47383d4 --- /dev/null +++ b/src/config/Slate/dropdownStyle.scss @@ -0,0 +1,9 @@ +.cms-ui .slate-inline-toolbar .ui.dropdown .menu > .item { + &.active { + color: #007eb1; + background-color: lighten(#007eb1, 60); + } + > .icon { + margin-right: 0; + } +} diff --git a/src/config/italiaConfig.js b/src/config/italiaConfig.js index 4d67d298d..7f6612b77 100644 --- a/src/config/italiaConfig.js +++ b/src/config/italiaConfig.js @@ -58,6 +58,7 @@ import bandoSVG from 'design-comuni-plone-theme/icons/bando.svg'; import logSVG from 'design-comuni-plone-theme/icons/log.svg'; import applyRichTextConfig from 'design-comuni-plone-theme/config/RichTextEditor/config'; +import applyItaliaSlateConfig from 'design-comuni-plone-theme/config/Slate/config'; import gdprPrivacyPanelConfig from 'design-comuni-plone-theme/config/volto-gdpr-privacy-defaultPanelConfig.js'; @@ -70,7 +71,8 @@ const ReleaseLog = loadable(() => ); export default function applyConfig(voltoConfig) { - let config = applyRichTextConfig(voltoConfig); + let config = applyRichTextConfig(voltoConfig); //[ToDO]: da rimuovere in favore di slate? + applyItaliaSlateConfig(config); /****************************************************************************** * SETTINGS From 4fc6d8acab86e08554876ae3797a7f122f140fab Mon Sep 17 00:00:00 2001 From: Giulia Ghisini Date: Tue, 26 Sep 2023 16:05:44 +0200 Subject: [PATCH 004/353] feat: added Align, Headings, Underline and Blockquote styles in slate editor --- src/config/Slate/Alignment/AlignMenu.jsx | 13 +- src/config/Slate/Alignment/index.js | 15 +- .../Slate/Blockquote/BlockquoteMenu.jsx | 138 ++++++++++++++++++ src/config/Slate/Blockquote/index.js | 12 ++ src/config/Slate/Headings/HeadingsMenu.jsx | 135 +++++++++++++++++ src/config/Slate/Headings/headingsMenu.scss | 44 ++++++ src/config/Slate/Headings/index.js | 36 +++++ src/config/Slate/Underline/index.js | 10 ++ src/config/Slate/config.js | 17 +++ .../{Alignment/utils.js => dropdownUtils.js} | 29 ++-- src/config/Slate/utils.js | 30 ++++ src/theme/ItaliaTheme/_main.scss | 15 +- 12 files changed, 460 insertions(+), 34 deletions(-) create mode 100644 src/config/Slate/Blockquote/BlockquoteMenu.jsx create mode 100644 src/config/Slate/Blockquote/index.js create mode 100644 src/config/Slate/Headings/HeadingsMenu.jsx create mode 100644 src/config/Slate/Headings/headingsMenu.scss create mode 100644 src/config/Slate/Headings/index.js create mode 100644 src/config/Slate/Underline/index.js rename src/config/Slate/{Alignment/utils.js => dropdownUtils.js} (86%) create mode 100644 src/config/Slate/utils.js diff --git a/src/config/Slate/Alignment/AlignMenu.jsx b/src/config/Slate/Alignment/AlignMenu.jsx index d7307ca4f..ded0e0117 100644 --- a/src/config/Slate/Alignment/AlignMenu.jsx +++ b/src/config/Slate/Alignment/AlignMenu.jsx @@ -4,7 +4,10 @@ import { Dropdown } from 'semantic-ui-react'; import { useIntl, defineMessages } from 'react-intl'; import cx from 'classnames'; import { omit } from 'lodash'; -import { isBlockStyleActive, toggleStyle } from './utils'; +import { + isBlockStyleActive, + toggleStyle, +} from 'design-comuni-plone-theme/config/Slate/dropdownUtils'; import { Icon } from '@plone/volto/components'; import { ToolbarButton } from '@plone/volto-slate/editor/ui'; @@ -14,7 +17,7 @@ import alignLeftSVG from '@plone/volto/icons/align-left.svg'; import alignRightSVG from '@plone/volto/icons/align-right.svg'; import alignJustifySVG from '@plone/volto/icons/align-justify.svg'; -import '../dropdownStyle.scss'; +import 'design-comuni-plone-theme/config/Slate/dropdownStyle.scss'; const ALIGN_OPTIONS = [ { @@ -28,7 +31,7 @@ const ALIGN_OPTIONS = [ icon: alignCenterSVG, }, { - cssClass: 'text-right', + cssClass: 'text-end', text: 'Allinea a destra', icon: alignRightSVG, }, @@ -63,8 +66,8 @@ const MenuOpts = ({ editor, toSelect, option, type }) => { onClick={(event, selItem) => { toggleStyle(editor, { cssClass: selItem.value, - isBlock: selItem.isBlock, - oneOfType: 'align', + isBlock: true, + oneOf: ALIGN_OPTIONS.reduce((acc, o) => [...acc, o.cssClass], []), }); }} /> diff --git a/src/config/Slate/Alignment/index.js b/src/config/Slate/Alignment/index.js index 9606f2ee0..0084df04d 100644 --- a/src/config/Slate/Alignment/index.js +++ b/src/config/Slate/Alignment/index.js @@ -1,10 +1,8 @@ import React from 'react'; import AlignMenu from './AlignMenu'; -//import '@plone/volto-slate/editor/plugins/StyleMenu/style.less'; export const AlignElement = ({ attributes, children, element }) => { - console.log(attributes, element); - return

aaaa{children}

; + return

{children}

; }; export default function install(config) { @@ -15,9 +13,14 @@ export default function install(config) { ); slate.elements['align'] = AlignElement; - slate.toolbarButtons.push('align'); - slate.expandedToolbarButtons.push('align'); - console.log(slate); + + //lo metto come primo elemento della toolbar + slate.toolbarButtons = ['align', 'separator', ...slate.toolbarButtons]; + slate.expandedToolbarButtons = [ + 'align', + 'separator', + ...slate.expandedToolbarButtons, + ]; return config; } diff --git a/src/config/Slate/Blockquote/BlockquoteMenu.jsx b/src/config/Slate/Blockquote/BlockquoteMenu.jsx new file mode 100644 index 000000000..fa2831189 --- /dev/null +++ b/src/config/Slate/Blockquote/BlockquoteMenu.jsx @@ -0,0 +1,138 @@ +import React from 'react'; +import { useSlate } from 'slate-react'; +import { Dropdown } from 'semantic-ui-react'; +import { useIntl, defineMessages } from 'react-intl'; +import cx from 'classnames'; +import { omit } from 'lodash'; +import { + isBlockStyleActive, + toggleStyle, +} from 'design-comuni-plone-theme/config/Slate/dropdownUtils'; + +import { Icon } from '@plone/volto/components'; +import { ToolbarButton } from '@plone/volto-slate/editor/ui'; + +import quoteIcon from '@plone/volto/icons/quote.svg'; + +import 'design-comuni-plone-theme/config/Slate/dropdownStyle.scss'; + +const OPTIONS = [ + { + title: 'Blockquote semplice', + format: 'blockquote', + icon: quoteIcon, + }, + { + title: 'Blockquote card', + format: 'blockquote', + icon: quoteIcon, + className: 'blockquote-card', + }, + { + title: 'Blockquote card scuro', + format: 'blockquote', + icon: quoteIcon, + className: 'blockquote-card dark', + }, +]; + +const messages = defineMessages({ + blockquote: { + id: 'Blockquote', + defaultMessage: 'Blockquote', + }, +}); + +const BlockquoteMenuButton = ({ icon, active, ...props }) => ( + +); + +const MenuOpts = ({ editor, toSelect, option, type }) => { + const isActive = toSelect.includes(option); + + return ( + { + toggleStyle(editor, { + cssClass: selItem.value, + isBlock: true, + oneOf: OPTIONS.reduce((acc, o) => [...acc, o.cssClass], []), + }); + }} + /> + ); +}; + +const BlockquoteButton = (props) => { + const editor = useSlate(); + const intl = useIntl(); + + const blockOpts = OPTIONS.map((def) => { + return { + value: def.cssClass, + text: def.label, + icon: (props) => , + isBlock: true, + originalIcon: def.icon, + }; + }); + + // Calculating the initial selection. + const toSelect = []; + let selectedIcon = OPTIONS[0].icon; + + // block styles + for (const val of blockOpts) { + const ia = isBlockStyleActive(editor, val.value); + + if (ia) { + toSelect.push(val); + selectedIcon = val.originalIcon; + } + } + + const menuItemProps = { + toSelect, + editor, + }; + + const showMenu = blockOpts.length; + + return showMenu ? ( + 0} + /> + } + > + + {blockOpts.length && + blockOpts.map((option, index) => ( + + ))} + + + ) : ( + '' + ); +}; + +export default BlockquoteButton; diff --git a/src/config/Slate/Blockquote/index.js b/src/config/Slate/Blockquote/index.js new file mode 100644 index 000000000..becd912d4 --- /dev/null +++ b/src/config/Slate/Blockquote/index.js @@ -0,0 +1,12 @@ +import React from 'react'; +import BlockquoteMenu from './BlockquoteMenu'; + +export default function install(config) { + const { slate } = config.settings; + + slate.buttons.blockquote = (props) => ( + + ); + + return config; +} diff --git a/src/config/Slate/Headings/HeadingsMenu.jsx b/src/config/Slate/Headings/HeadingsMenu.jsx new file mode 100644 index 000000000..f1cdcc573 --- /dev/null +++ b/src/config/Slate/Headings/HeadingsMenu.jsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { useSlate } from 'slate-react'; +import { Dropdown } from 'semantic-ui-react'; +import { useIntl, defineMessages } from 'react-intl'; +import cx from 'classnames'; +import { omit } from 'lodash'; +import config from '@plone/volto/registry'; +import { isBlockActive } from '@plone/volto-slate/utils'; + +import { ToolbarButton, BlockButton } from '@plone/volto-slate/editor/ui'; + +import headingIcon from '@plone/volto/icons/heading.svg'; + +import 'design-comuni-plone-theme/config/Slate/dropdownStyle.scss'; +import 'design-comuni-plone-theme/config/Slate/Headings/headingsMenu.scss'; + +const OPTIONS = [ + { + title: 'h2', + format: 'h2', + content: 'h2', + className: 'headings-h2', + }, + { + title: 'h3', + format: 'h3', + content: 'h3', + className: 'headings-h3', + }, + { + title: 'h4', + format: 'h4', + content: 'h4', + className: 'headings-h4', + }, + { + title: 'h5', + format: 'h5', + content: 'h5', + className: 'headings-h5', + }, + { + title: 'h6', + format: 'h6', + content: 'h6', + className: 'headings-h6', + }, +]; + +const messages = defineMessages({ + title: { + id: 'Titolo', + defaultMessage: 'Titolo', + }, +}); + +const HeadingsMenuButton = ({ icon, active, ...props }) => ( + +); + +const MenuOpts = ({ editor, toSelect, option, type }) => { + const isActive = toSelect.includes(option); + return ( + + + + ); +}; + +const HeadingsButton = (props) => { + const editor = useSlate(); + const intl = useIntl(); + + // Calculating the initial selection. + const toSelect = []; + + // block styles + for (const opt of OPTIONS) { + const ia = isBlockActive(editor, opt.format); + + if (ia) { + toSelect.push(opt); + } + } + + const menuItemProps = { + toSelect, + editor, + }; + + const showMenu = OPTIONS.length; + + return showMenu ? ( + 0} + /> + } + > + + {OPTIONS.length > 0 && + OPTIONS.map((option, index) => ( + + ))} + + + ) : ( + '' + ); +}; + +export default HeadingsButton; diff --git a/src/config/Slate/Headings/headingsMenu.scss b/src/config/Slate/Headings/headingsMenu.scss new file mode 100644 index 000000000..ac898bdb5 --- /dev/null +++ b/src/config/Slate/Headings/headingsMenu.scss @@ -0,0 +1,44 @@ +.slate-toolbar .ui.dropdown#headings-menu .menu { + .item { + padding: 0 !important; + text-align: center; + .button-wrapper { + margin: 0; + .ui.button { + border: none; + border-radius: 0; + padding: 0.78571429rem 1.14285714rem !important; + + &, + &.active { + background-color: transparent !important; + color: rgba(0, 0, 0, 0.6) !important; + } + + &.headings-h2 { + font-size: 2.222rem; + font-family: 'Titillium Web', Geneva, Tahoma, sans-serif; + } + &.headings-h3 { + font-size: 1.777rem; + font-family: 'Titillium Web', Geneva, Tahoma, sans-serif; + } + &.headings-h4 { + font-size: 1.555rem; + font-weight: 600; + font-family: 'Titillium Web', Geneva, Tahoma, sans-serif; + } + &.headings-h5 { + font-size: 1.333rem; + font-weight: 400; + font-family: 'Titillium Web', Geneva, Tahoma, sans-serif; + } + &.headings-h6 { + font-size: 1rem; + font-weight: 600; + font-family: 'Titillium Web', Geneva, Tahoma, sans-serif; + } + } + } + } +} diff --git a/src/config/Slate/Headings/index.js b/src/config/Slate/Headings/index.js new file mode 100644 index 000000000..5d121aaa9 --- /dev/null +++ b/src/config/Slate/Headings/index.js @@ -0,0 +1,36 @@ +import React from 'react'; +import HeadingsMenu from './HeadingsMenu'; +import { insertToolbarButtons } from 'design-comuni-plone-theme/config/Slate/utils'; + +export default function install(config) { + const { slate } = config.settings; + + slate.buttons.headings = (props) => ( + + ); + + //aggiungo gli elements h5, h6 perchè non previsti da volto + slate.elements['h5'] = ({ attributes, children }) => ( +
{children}
+ ); + slate.elements['h6'] = ({ attributes, children }) => ( +
{children}
+ ); + + // rimuovo i bottoni di heading di volto + slate.toolbarButtons = slate.toolbarButtons.filter( + (b) => b !== 'heading-two' && b !== 'heading-three', + ); + slate.expandedToolbarButtons = slate.expandedToolbarButtons.filter( + (b) => b !== 'heading-two' && b !== 'heading-three', + ); + + //aggiungo il bottone di headings alla toolbar, dopo strikethrough + insertToolbarButtons( + ['separator', 'headings', 'separator'], + 'strikethrough', + slate, + ); + + return config; +} diff --git a/src/config/Slate/Underline/index.js b/src/config/Slate/Underline/index.js new file mode 100644 index 000000000..eb5b9efba --- /dev/null +++ b/src/config/Slate/Underline/index.js @@ -0,0 +1,10 @@ +import { insertToolbarButtons } from 'design-comuni-plone-theme/config/Slate/utils'; + +export default function install(config) { + const { slate } = config.settings; + + //aggiungo il bottone di underline alla toolbar, dopo bold + insertToolbarButtons(['underline'], 'italic', slate); + + return config; +} diff --git a/src/config/Slate/config.js b/src/config/Slate/config.js index b55fb2283..24f665ad3 100644 --- a/src/config/Slate/config.js +++ b/src/config/Slate/config.js @@ -1,6 +1,23 @@ //config.settings.slate.contextToolbarButtons import installAlignment from 'design-comuni-plone-theme/config/Slate/Alignment'; +import installHeadings from 'design-comuni-plone-theme/config/Slate/Headings'; +import installUnderline from 'design-comuni-plone-theme/config/Slate/Underline'; +import installBlockquote from 'design-comuni-plone-theme/config/Slate/Blockquote'; export default function applyItaliaSlateConfig(config) { installAlignment(config); + installHeadings(config); + installUnderline(config); + + installBlockquote(config); + + //remove callout because there's a Volto's block for it + delete config.settings.slate.elements.callout; + delete config.settings.slate.buttons.callout; + config.settings.slate.toolbarButtons = config.settings.slate.toolbarButtons.filter( + (b) => b !== 'callout', + ); + config.settings.slate.expandedToolbarButtons = config.settings.slate.toolbarButtons.filter( + (b) => b !== 'callout', + ); } diff --git a/src/config/Slate/Alignment/utils.js b/src/config/Slate/dropdownUtils.js similarity index 86% rename from src/config/Slate/Alignment/utils.js rename to src/config/Slate/dropdownUtils.js index ce42c5aab..dd485a851 100644 --- a/src/config/Slate/Alignment/utils.js +++ b/src/config/Slate/dropdownUtils.js @@ -9,15 +9,15 @@ import config from '@plone/volto/registry'; * @param {object} options * @param {boolean} options.oneOfType Whether the given type is admitted once */ -export const toggleStyle = (editor, { cssClass, isBlock, oneOfType }) => { +export const toggleStyle = (editor, { cssClass, isBlock, oneOf }) => { if (isBlock) { - toggleBlockStyle(editor, cssClass, oneOfType); + toggleBlockStyle(editor, cssClass, oneOf); } else { toggleInlineStyle(editor, cssClass); } }; -export const toggleBlockStyle = (editor, style, oneOfType) => { +export const toggleBlockStyle = (editor, style, oneOf) => { // We have 6 boolean variables which need to be accounted for. // See https://docs.google.com/spreadsheets/d/1mVeMuqSTMABV2BhoHPrPAFjn7zUksbNgZ9AQK_dcd3U/edit?usp=sharing const { slate } = config.settings; @@ -33,7 +33,7 @@ export const toggleBlockStyle = (editor, style, oneOfType) => { } else if (!isListItem && wantsList) { // changeBlockToList(editor, format); } else if (!isListItem && !wantsList) { - internalToggleBlockStyle(editor, style); + internalToggleBlockStyle(editor, style, oneOf); } else { console.warn('toggleBlockStyle case not covered, please examine:', { wantsList, @@ -104,8 +104,8 @@ export const isInlineStyleActive = (editor, style) => { return false; }; -export const internalToggleBlockStyle = (editor, style) => { - toggleBlockStyleInSelection(editor, style); +export const internalToggleBlockStyle = (editor, style, oneOf) => { + toggleBlockStyleInSelection(editor, style, oneOf); }; export const internalToggleInlineStyle = (editor, style) => { @@ -138,7 +138,7 @@ function toggleInlineStyleInSelection(editor, style) { } } -function toggleBlockStyleInSelection(editor, style) { +function toggleBlockStyleInSelection(editor, style, oneOf) { const sn = Array.from( Editor.nodes(editor, { mode: 'highest', @@ -155,11 +155,22 @@ function toggleBlockStyleInSelection(editor, style) { } else if (n.styleName.split(' ').filter((x) => x === style).length > 0) { cn = cn .split(' ') - .filter((x) => x !== style) + .filter((x) => x !== style && x !== '') .join(' '); } else { + if (oneOf?.length > 0) { + cn = cn + .split(' ') + .filter((c) => !oneOf.includes(c)) + .join(' '); + } + // the style is not set but other styles are set - cn = cn.split(' ').concat(style).join(' '); + cn = cn + .split(' ') + .filter((c) => c !== '') + .concat(style) + .join(' '); } Transforms.setNodes(editor, { styleName: cn }, { at: p }); } diff --git a/src/config/Slate/utils.js b/src/config/Slate/utils.js new file mode 100644 index 000000000..9dfc962d2 --- /dev/null +++ b/src/config/Slate/utils.js @@ -0,0 +1,30 @@ +export const insertToolbarButtons = (buttons = [], insertAfter = '', slate) => { + const insertAtToolbarButtons = slate.toolbarButtons.indexOf(insertAfter) + 1; + slate.toolbarButtons = [ + ...slate.toolbarButtons.slice(0, insertAtToolbarButtons), + ...buttons, + ...slate.toolbarButtons.slice(insertAtToolbarButtons), + ].filter((el, index, array) => { + if (index > 0) { + //rimuovo i separatori consecutivi nel caso se ne siano creati + if (el === 'separator' && array[index - 1] === 'separator') return false; + } + return true; + }); + + const insertAtExpandedToolbarButtons = + slate.toolbarButtons.indexOf(insertAfter) + 1; + slate.expandedToolbarButtons = [ + ...slate.expandedToolbarButtons.slice(0, insertAtExpandedToolbarButtons), + ...buttons, + ...slate.expandedToolbarButtons.slice(insertAtExpandedToolbarButtons), + ].filter((el, index, array) => { + if (index > 0) { + //rimuovo i separatori consecutivi nel caso se ne siano creati + if (el === 'separator' && array[index - 1] === 'separator') return false; + } + return true; + }); + + //rimuovo doppi separator +}; diff --git a/src/theme/ItaliaTheme/_main.scss b/src/theme/ItaliaTheme/_main.scss index 432e2f3db..762b0c360 100644 --- a/src/theme/ItaliaTheme/_main.scss +++ b/src/theme/ItaliaTheme/_main.scss @@ -89,20 +89,7 @@ iframe { .public-ui, .cms-ui { blockquote { - position: relative; - margin: 1.5rem 2rem; - font-family: $font-family-serif; - - &:before { - position: absolute; - top: -1.8rem; - left: -2rem; - display: block; - height: 0; - color: $neutral-2-b2; - content: '“'; - font-size: 400%; - } + border-color: $primary !important; } .callout { From f084598dfb80b75238901667f7a20bd7ced94fce Mon Sep 17 00:00:00 2001 From: Giulia Ghisini Date: Thu, 28 Sep 2023 16:02:44 +0200 Subject: [PATCH 005/353] chore: blockquote slate --- src/config/Slate/Alignment/AlignMenu.jsx | 11 ++- .../Slate/Blockquote/BlockquoteMenu.jsx | 32 ++++--- src/config/Slate/dropdownUtils.js | 93 ++++++++++++++++--- src/icons/Group.svg | 1 + src/icons/blockquote-card-dark.svg | 1 + src/icons/blockquote-card.svg | 1 + src/icons/blockquote-simple.svg | 1 + 7 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 src/icons/Group.svg create mode 100644 src/icons/blockquote-card-dark.svg create mode 100644 src/icons/blockquote-card.svg create mode 100644 src/icons/blockquote-simple.svg diff --git a/src/config/Slate/Alignment/AlignMenu.jsx b/src/config/Slate/Alignment/AlignMenu.jsx index ded0e0117..774460ebe 100644 --- a/src/config/Slate/Alignment/AlignMenu.jsx +++ b/src/config/Slate/Alignment/AlignMenu.jsx @@ -22,22 +22,22 @@ import 'design-comuni-plone-theme/config/Slate/dropdownStyle.scss'; const ALIGN_OPTIONS = [ { cssClass: 'text-left', - text: 'Allinea a sinistra', + title: 'Allinea a sinistra', icon: alignLeftSVG, }, { cssClass: 'text-center', - text: 'Allinea al centro', + title: 'Allinea al centro', icon: alignCenterSVG, }, { cssClass: 'text-end', - text: 'Allinea a destra', + title: 'Allinea a destra', icon: alignRightSVG, }, { cssClass: 'text-justify', - text: 'Allinea il testo giustificato', + title: 'Allinea il testo giustificato', icon: alignJustifySVG, }, ]; @@ -81,7 +81,8 @@ const AlignButton = (props) => { const blockOpts = ALIGN_OPTIONS.map((def) => { return { value: def.cssClass, - text: def.label, + text: def.text, + title: def.title, icon: (props) => , isBlock: true, originalIcon: def.icon, diff --git a/src/config/Slate/Blockquote/BlockquoteMenu.jsx b/src/config/Slate/Blockquote/BlockquoteMenu.jsx index fa2831189..62f87c211 100644 --- a/src/config/Slate/Blockquote/BlockquoteMenu.jsx +++ b/src/config/Slate/Blockquote/BlockquoteMenu.jsx @@ -13,6 +13,9 @@ import { Icon } from '@plone/volto/components'; import { ToolbarButton } from '@plone/volto-slate/editor/ui'; import quoteIcon from '@plone/volto/icons/quote.svg'; +import blockquoteSimpleIcon from 'design-comuni-plone-theme/icons/blockquote-simple.svg'; +import blockquoteCardIcon from 'design-comuni-plone-theme/icons/blockquote-card.svg'; +import blockquoteCardDarkIcon from 'design-comuni-plone-theme/icons/blockquote-card-dark.svg'; import 'design-comuni-plone-theme/config/Slate/dropdownStyle.scss'; @@ -20,19 +23,20 @@ const OPTIONS = [ { title: 'Blockquote semplice', format: 'blockquote', - icon: quoteIcon, + icon: blockquoteSimpleIcon, + cssClass: 'blockquote', }, { title: 'Blockquote card', format: 'blockquote', - icon: quoteIcon, - className: 'blockquote-card', + icon: blockquoteCardIcon, + cssClass: 'blockquote-card', }, { title: 'Blockquote card scuro', format: 'blockquote', - icon: quoteIcon, - className: 'blockquote-card dark', + icon: blockquoteCardDarkIcon, + cssClass: 'blockquote-card dark', }, ]; @@ -47,7 +51,7 @@ const BlockquoteMenuButton = ({ icon, active, ...props }) => ( ); -const MenuOpts = ({ editor, toSelect, option, type }) => { +const MenuOpts = ({ editor, toSelect, option, type, ...props }) => { const isActive = toSelect.includes(option); return ( @@ -61,6 +65,8 @@ const MenuOpts = ({ editor, toSelect, option, type }) => { toggleStyle(editor, { cssClass: selItem.value, isBlock: true, + format: selItem.format, + allowedChildren: props.allowedChildren, oneOf: OPTIONS.reduce((acc, o) => [...acc, o.cssClass], []), }); }} @@ -75,7 +81,9 @@ const BlockquoteButton = (props) => { const blockOpts = OPTIONS.map((def) => { return { value: def.cssClass, - text: def.label, + text: def.text, + title: def.title, + format: def.format, icon: (props) => , isBlock: true, originalIcon: def.icon, @@ -84,11 +92,12 @@ const BlockquoteButton = (props) => { // Calculating the initial selection. const toSelect = []; - let selectedIcon = OPTIONS[0].icon; + let selectedIcon = quoteIcon; + const oneOf = OPTIONS.reduce((acc, o) => [...acc, o.cssClass], []); // block styles for (const val of blockOpts) { - const ia = isBlockStyleActive(editor, val.value); + const ia = isBlockStyleActive(editor, val.value, oneOf); if (ia) { toSelect.push(val); @@ -99,13 +108,14 @@ const BlockquoteButton = (props) => { const menuItemProps = { toSelect, editor, + ...props, }; const showMenu = blockOpts.length; return showMenu ? ( { blockOpts.map((option, index) => ( diff --git a/src/config/Slate/dropdownUtils.js b/src/config/Slate/dropdownUtils.js index dd485a851..e84f1318b 100644 --- a/src/config/Slate/dropdownUtils.js +++ b/src/config/Slate/dropdownUtils.js @@ -1,6 +1,6 @@ /* eslint no-console: ["error", { allow: ["warn", "error"] }] */ import { Editor, Transforms } from 'slate'; -import { isBlockActive } from '@plone/volto-slate/utils'; +import { isBlockActive, toggleFormat } from '@plone/volto-slate/utils'; import config from '@plone/volto/registry'; /** @@ -9,15 +9,24 @@ import config from '@plone/volto/registry'; * @param {object} options * @param {boolean} options.oneOfType Whether the given type is admitted once */ -export const toggleStyle = (editor, { cssClass, isBlock, oneOf }) => { +export const toggleStyle = ( + editor, + { cssClass, isBlock, oneOf, format, allowedChildren }, +) => { if (isBlock) { - toggleBlockStyle(editor, cssClass, oneOf); + toggleBlockStyle(editor, cssClass, oneOf, format, allowedChildren); } else { toggleInlineStyle(editor, cssClass); } }; -export const toggleBlockStyle = (editor, style, oneOf) => { +export const toggleBlockStyle = ( + editor, + style, + oneOf, + format, + allowedChildren, +) => { // We have 6 boolean variables which need to be accounted for. // See https://docs.google.com/spreadsheets/d/1mVeMuqSTMABV2BhoHPrPAFjn7zUksbNgZ9AQK_dcd3U/edit?usp=sharing const { slate } = config.settings; @@ -33,7 +42,13 @@ export const toggleBlockStyle = (editor, style, oneOf) => { } else if (!isListItem && wantsList) { // changeBlockToList(editor, format); } else if (!isListItem && !wantsList) { - internalToggleBlockStyle(editor, style, oneOf); + console.log('before: ', editor.children[0].styleName + ''); + if (format && editor.children?.[0]?.type !== format) { + toggleFormat(editor, format, allowedChildren); + } + internalToggleBlockStyle(editor, style, oneOf, format); + console.log('after:', editor.children[0].styleName + ''); + //sse ho rimosso tutti gli stili devo rimuoverre anche il format } else { console.warn('toggleBlockStyle case not covered, please examine:', { wantsList, @@ -69,7 +84,7 @@ export const toggleInlineStyle = (editor, style) => { } }; -export const isBlockStyleActive = (editor, style) => { +export const isBlockStyleActive = (editor, style, oneOf) => { const keyName = `style-${style}`; const sn = Array.from( Editor.nodes(editor, { @@ -81,9 +96,35 @@ export const isBlockStyleActive = (editor, style) => { }), ); + if (oneOf) { + oneOf.sort((a, b) => { + const al = a.split(' ').length + a.split('-').length; + const bl = b.split(' ').length + b.split('-').length; + return al > bl ? -1 : al < bl ? 1 : 0; + }); + } + for (const [n] of sn) { if (typeof n.styleName === 'string') { - if (n.styleName.split(' ').filter((x) => x === style).length > 0) { + if (oneOf?.length > 0) { + let selected = false; + let foundOneOf = false; + + for (var i = 0; i < oneOf.length; i++) { + const o = oneOf[i]; + + const si = n.styleName.indexOf(o); + if (si >= 0) { + if (!foundOneOf) { + foundOneOf = true; + if (o === style) { + selected = true; + } + } + } + } + return selected; + } else if (n.styleName.split(' ').filter((x) => x === style).length > 0) { return true; } } else if ( @@ -104,8 +145,8 @@ export const isInlineStyleActive = (editor, style) => { return false; }; -export const internalToggleBlockStyle = (editor, style, oneOf) => { - toggleBlockStyleInSelection(editor, style, oneOf); +export const internalToggleBlockStyle = (editor, style, oneOf, format) => { + toggleBlockStyleInSelection(editor, style, oneOf, format); }; export const internalToggleInlineStyle = (editor, style) => { @@ -138,7 +179,7 @@ function toggleInlineStyleInSelection(editor, style) { } } -function toggleBlockStyleInSelection(editor, style, oneOf) { +function toggleBlockStyleInSelection(editor, style, oneOf, format) { const sn = Array.from( Editor.nodes(editor, { mode: 'highest', @@ -148,24 +189,54 @@ function toggleBlockStyleInSelection(editor, style, oneOf) { }), ); + //ordino mettendo prima le combinazioni di classi + oneOf.sort((a, b) => { + const al = a.split(' ').length + a.split('-').length; + const bl = b.split(' ').length + b.split('-').length; + return al > bl ? -1 : al < bl ? 1 : 0; + }); + for (const [n, p] of sn) { let cn = n.styleName; + if (typeof n.styleName !== 'string') { cn = style; + } else if (oneOf.indexOf(style) >= 0) { + let stylename = n.styleName; + let addStyle = true; + oneOf.forEach((o) => { + const si = stylename.indexOf(o); + if (si >= 0) { + addStyle = o !== style; + stylename = stylename.replace(o, ''); + } + }); + + //rimuovo gli stili oneof + cn = stylename.split(' ').filter((x) => x !== ''); + + if (addStyle) { + cn = cn.concat(style); + } else if (format) { + toggleFormat(editor, format); + } + cn = cn.join(' '); } else if (n.styleName.split(' ').filter((x) => x === style).length > 0) { + //rimuovo lo stile style cn = cn .split(' ') .filter((x) => x !== style && x !== '') .join(' '); } else { if (oneOf?.length > 0) { + //tolgo tutti gli altri stili di oneOf, perchè ne voglio solo uno di quelli cn = cn .split(' ') .filter((c) => !oneOf.includes(c)) .join(' '); } - // the style is not set but other styles are set + // the style is not set but other styles are set. Aggiungo lo stile cn = cn .split(' ') .filter((c) => c !== '') diff --git a/src/icons/Group.svg b/src/icons/Group.svg new file mode 100644 index 000000000..32828583d --- /dev/null +++ b/src/icons/Group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/blockquote-card-dark.svg b/src/icons/blockquote-card-dark.svg new file mode 100644 index 000000000..49b5f9c64 --- /dev/null +++ b/src/icons/blockquote-card-dark.svg @@ -0,0 +1 @@ + diff --git a/src/icons/blockquote-card.svg b/src/icons/blockquote-card.svg new file mode 100644 index 000000000..9a123fd32 --- /dev/null +++ b/src/icons/blockquote-card.svg @@ -0,0 +1 @@ + diff --git a/src/icons/blockquote-simple.svg b/src/icons/blockquote-simple.svg new file mode 100644 index 000000000..51366f8fc --- /dev/null +++ b/src/icons/blockquote-simple.svg @@ -0,0 +1 @@ + From a9dc7b21c4601d4194031dc3164664d79cb080cb Mon Sep 17 00:00:00 2001 From: Giulia Ghisini Date: Fri, 29 Sep 2023 15:04:45 +0200 Subject: [PATCH 006/353] chore: slate link style button and blockquotes icon --- .../Slate/Blockquote/BlockquoteMenu.jsx | 2 +- src/config/Slate/Link/renderer.jsx | 58 +++++++ src/config/Slate/LinkButton/headingsMenu.scss | 44 ++++++ src/config/Slate/LinkButton/index.js | 44 ++++++ src/config/Slate/config.js | 3 + src/config/Slate/dropdownUtils.js | 146 ++++++++++++++++-- src/icons/blockquote-card-dark.svg | 4 +- src/icons/blockquote-card.svg | 4 +- src/icons/blockquote-simple.svg | 4 +- src/theme/ItaliaTheme/_main.scss | 34 ++++ 10 files changed, 328 insertions(+), 15 deletions(-) create mode 100644 src/config/Slate/Link/renderer.jsx create mode 100644 src/config/Slate/LinkButton/headingsMenu.scss create mode 100644 src/config/Slate/LinkButton/index.js diff --git a/src/config/Slate/Blockquote/BlockquoteMenu.jsx b/src/config/Slate/Blockquote/BlockquoteMenu.jsx index 62f87c211..deae16b09 100644 --- a/src/config/Slate/Blockquote/BlockquoteMenu.jsx +++ b/src/config/Slate/Blockquote/BlockquoteMenu.jsx @@ -92,7 +92,7 @@ const BlockquoteButton = (props) => { // Calculating the initial selection. const toSelect = []; - let selectedIcon = quoteIcon; + let selectedIcon = blockquoteCardIcon; const oneOf = OPTIONS.reduce((acc, o) => [...acc, o.cssClass], []); // block styles diff --git a/src/config/Slate/Link/renderer.jsx b/src/config/Slate/Link/renderer.jsx new file mode 100644 index 000000000..e44cd62ef --- /dev/null +++ b/src/config/Slate/Link/renderer.jsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { UniversalLink } from '@plone/volto/components'; +import config from '@plone/volto/registry'; +import { isInternalURL, flattenToAppURL } from '@plone/volto/helpers'; + +const ViewLink = ({ url, target, download, children, className }) => { + const { openExternalLinkInNewTab } = config.settings; + return ( + + {children} + + ); +}; + +export const LinkElement = (props) => { + const { attributes, children, element, mode = 'edit' } = props; + + return mode === 'view' ? ( + + {children} + + ) : ( + e.preventDefault()} + > + {Array.isArray(children) + ? children.map((child, i) => { + if (child?.props?.decorations) { + const isSelection = + child.props.decorations.findIndex((deco) => deco.isSelection) > + -1; + if (isSelection) + return ( + + {child} + + ); + } + return child; + }) + : children} + + ); +}; diff --git a/src/config/Slate/LinkButton/headingsMenu.scss b/src/config/Slate/LinkButton/headingsMenu.scss new file mode 100644 index 000000000..ac898bdb5 --- /dev/null +++ b/src/config/Slate/LinkButton/headingsMenu.scss @@ -0,0 +1,44 @@ +.slate-toolbar .ui.dropdown#headings-menu .menu { + .item { + padding: 0 !important; + text-align: center; + .button-wrapper { + margin: 0; + .ui.button { + border: none; + border-radius: 0; + padding: 0.78571429rem 1.14285714rem !important; + + &, + &.active { + background-color: transparent !important; + color: rgba(0, 0, 0, 0.6) !important; + } + + &.headings-h2 { + font-size: 2.222rem; + font-family: 'Titillium Web', Geneva, Tahoma, sans-serif; + } + &.headings-h3 { + font-size: 1.777rem; + font-family: 'Titillium Web', Geneva, Tahoma, sans-serif; + } + &.headings-h4 { + font-size: 1.555rem; + font-weight: 600; + font-family: 'Titillium Web', Geneva, Tahoma, sans-serif; + } + &.headings-h5 { + font-size: 1.333rem; + font-weight: 400; + font-family: 'Titillium Web', Geneva, Tahoma, sans-serif; + } + &.headings-h6 { + font-size: 1rem; + font-weight: 600; + font-family: 'Titillium Web', Geneva, Tahoma, sans-serif; + } + } + } + } +} diff --git a/src/config/Slate/LinkButton/index.js b/src/config/Slate/LinkButton/index.js new file mode 100644 index 000000000..f9515c79a --- /dev/null +++ b/src/config/Slate/LinkButton/index.js @@ -0,0 +1,44 @@ +import React from 'react'; +import { useSlate } from 'slate-react'; + +import { ToolbarButton } from '@plone/volto-slate/editor/ui'; + +import circleMenuSVG from '@plone/volto/icons/circle-menu.svg'; + +import { + toggleStyle, + isLinkStyleActive, +} from 'design-comuni-plone-theme/config/Slate/dropdownUtils'; +import { insertToolbarButtons } from 'design-comuni-plone-theme/config/Slate/utils'; +import { LinkElement as ItaliaLinkElement } from 'design-comuni-plone-theme/config/Slate/Link/renderer'; + +const LinkButtonButton = ({ icon, active, ...props }) => { + const CLASSNAME = 'btn btn-primary inline-link'; + const editor = useSlate(); + const isActive = isLinkStyleActive(editor, CLASSNAME); + + return ( + + toggleStyle(editor, { cssClass: CLASSNAME, isBlock: false }) + } + /> + ); +}; + +export default function install(config) { + const { slate } = config.settings; + + slate.buttons.linkButton = (props) => ( + + ); + slate.elements.link = ItaliaLinkElement; + + //aggiungo il bottone di headings alla toolbar, dopo strikethrough + insertToolbarButtons(['linkButton'], 'link', slate); + + return config; +} diff --git a/src/config/Slate/config.js b/src/config/Slate/config.js index 24f665ad3..eee111d2d 100644 --- a/src/config/Slate/config.js +++ b/src/config/Slate/config.js @@ -3,6 +3,7 @@ import installAlignment from 'design-comuni-plone-theme/config/Slate/Alignment'; import installHeadings from 'design-comuni-plone-theme/config/Slate/Headings'; import installUnderline from 'design-comuni-plone-theme/config/Slate/Underline'; import installBlockquote from 'design-comuni-plone-theme/config/Slate/Blockquote'; +import installLinkButton from 'design-comuni-plone-theme/config/Slate/LinkButton'; export default function applyItaliaSlateConfig(config) { installAlignment(config); @@ -10,6 +11,8 @@ export default function applyItaliaSlateConfig(config) { installUnderline(config); installBlockquote(config); + installLinkButton(config); + //remove callout because there's a Volto's block for it delete config.settings.slate.elements.callout; diff --git a/src/config/Slate/dropdownUtils.js b/src/config/Slate/dropdownUtils.js index e84f1318b..05db4fd58 100644 --- a/src/config/Slate/dropdownUtils.js +++ b/src/config/Slate/dropdownUtils.js @@ -16,7 +16,7 @@ export const toggleStyle = ( if (isBlock) { toggleBlockStyle(editor, cssClass, oneOf, format, allowedChildren); } else { - toggleInlineStyle(editor, cssClass); + toggleInlineStyle(editor, cssClass, oneOf); } }; @@ -32,6 +32,7 @@ export const toggleBlockStyle = ( const { slate } = config.settings; const isListItem = isBlockActive(editor, slate.listItemType); + const isActive = isBlockStyleActive(editor, style); const wantsList = false; @@ -42,12 +43,10 @@ export const toggleBlockStyle = ( } else if (!isListItem && wantsList) { // changeBlockToList(editor, format); } else if (!isListItem && !wantsList) { - console.log('before: ', editor.children[0].styleName + ''); if (format && editor.children?.[0]?.type !== format) { toggleFormat(editor, format, allowedChildren); } internalToggleBlockStyle(editor, style, oneOf, format); - console.log('after:', editor.children[0].styleName + ''); //sse ho rimosso tutti gli stili devo rimuoverre anche il format } else { console.warn('toggleBlockStyle case not covered, please examine:', { @@ -58,16 +57,20 @@ export const toggleBlockStyle = ( } }; -export const toggleInlineStyle = (editor, style) => { +export const toggleInlineStyle = (editor, style, oneOf) => { // We have 6 boolean variables which need to be accounted for. // See https://docs.google.com/spreadsheets/d/1mVeMuqSTMABV2BhoHPrPAFjn7zUksbNgZ9AQK_dcd3U/edit?usp=sharing const { slate } = config.settings; const isListItem = isBlockActive(editor, slate.listItemType); + const isLinkItem = isBlockActive(editor, 'link'); + const isActive = isInlineStyleActive(editor, style); const wantsList = false; - if (isListItem && !wantsList) { + if (isLinkItem) { + toggleLinkStyleInSelection(editor, style, oneOf); + } else if (isListItem && !wantsList) { toggleInlineStyleAsListItem(editor, style); } else if (isListItem && wantsList && !isActive) { // switchListType(editor, format); // this will deconstruct to Volto blocks @@ -139,12 +142,62 @@ export const isBlockStyleActive = (editor, style, oneOf) => { export const isInlineStyleActive = (editor, style) => { const m = Editor.marks(editor); const keyName = `style-${style}`; + if (m && m[keyName]) { return true; } return false; }; +export const isLinkStyleActive = (editor, style) => { + const selection = editor.selection || editor.getSavedSelection(); + let found; + try { + found = Array.from( + Editor.nodes(editor, { + match: (n) => n.type === 'link', + at: selection, + }) || [], + ); + } catch (e) { + // eslint-disable-next-line + // console.warn('Error in finding active element', e); + return false; + } + + if (found.length) { + for (const [n, p] of found) { + if (n?.styleName?.indexOf(style) >= 0) { + return true; + } + } + } + + if (selection) { + const { path } = selection.anchor; + const isAtStart = + selection.anchor.offset === 0 && selection.focus.offset === 0; + + if (isAtStart) { + try { + found = Editor.previous(editor, { + at: path, + // match: (n) => n.type === MENTION, + }); + } catch (ex) { + found = []; + } + if (found && found[0] && found[0].type === 'link') { + if (found[0]?.styleName?.indexOf(style) >= 0) { + return true; + } + } + } + } + + return false; +}; + export const internalToggleBlockStyle = (editor, style, oneOf, format) => { toggleBlockStyleInSelection(editor, style, oneOf, format); }; @@ -179,6 +232,75 @@ function toggleInlineStyleInSelection(editor, style) { } } +function toggleLinkStyleInSelection(editor, style, oneOf) { + const sn = Array.from( + Editor.nodes(editor, { + mode: 'all', + match: (n) => { + return !Editor.isEditor(n) && n.type === 'link'; + }, + }), + ); + + //ordino mettendo prima le combinazioni di classi + if (oneOf) { + oneOf.sort((a, b) => { + const al = a.split(' ').length + a.split('-').length; + const bl = b.split(' ').length + b.split('-').length; + return al > bl ? -1 : al < bl ? 1 : 0; + }); + } + + for (const [n, p] of sn) { + let cn = n.styleName; + + if (typeof n.styleName !== 'string') { + cn = style; + } else if (oneOf && oneOf.indexOf(style) >= 0) { + let stylename = n.styleName; + let addStyle = true; + oneOf.forEach((o) => { + const si = stylename.indexOf(o); + if (si >= 0) { + addStyle = o !== style; + stylename = stylename.replace(o, ''); + } + }); + + //rimuovo gli stili oneof + cn = stylename.split(' ').filter((x) => x !== ''); + + if (addStyle) { + cn = cn.concat(style); + } + cn = cn.join(' '); + } else if (n.styleName.indexOf(style) >= 0) { + //rimuovo lo stile style + cn = n.styleName + .replace(style, '') + .split(' ') + .filter((x) => x !== '') + .join(' '); + } else { + if (oneOf?.length > 0) { + //tolgo tutti gli altri stili di oneOf, perchè ne voglio solo uno di quelli + cn = cn + .split(' ') + .filter((c) => !oneOf.includes(c)) + .join(' '); + } + + // the style is not set but other styles are set. Aggiungo lo stile + cn = cn + .split(' ') + .filter((c) => c !== '') + .concat(style) + .join(' '); + } + Transforms.setNodes(editor, { styleName: cn }, { at: p }); + } +} + function toggleBlockStyleInSelection(editor, style, oneOf, format) { const sn = Array.from( Editor.nodes(editor, { @@ -190,18 +312,20 @@ function toggleBlockStyleInSelection(editor, style, oneOf, format) { ); //ordino mettendo prima le combinazioni di classi - oneOf.sort((a, b) => { - const al = a.split(' ').length + a.split('-').length; - const bl = b.split(' ').length + b.split('-').length; - return al > bl ? -1 : al < bl ? 1 : 0; - }); + if (oneOf) { + oneOf.sort((a, b) => { + const al = a.split(' ').length + a.split('-').length; + const bl = b.split(' ').length + b.split('-').length; + return al > bl ? -1 : al < bl ? 1 : 0; + }); + } for (const [n, p] of sn) { let cn = n.styleName; if (typeof n.styleName !== 'string') { cn = style; - } else if (oneOf.indexOf(style) >= 0) { + } else if (oneOf && oneOf.indexOf(style) >= 0) { let stylename = n.styleName; let addStyle = true; oneOf.forEach((o) => { diff --git a/src/icons/blockquote-card-dark.svg b/src/icons/blockquote-card-dark.svg index 49b5f9c64..781df1974 100644 --- a/src/icons/blockquote-card-dark.svg +++ b/src/icons/blockquote-card-dark.svg @@ -1 +1,3 @@ - + + abc + diff --git a/src/icons/blockquote-card.svg b/src/icons/blockquote-card.svg index 9a123fd32..4e62e534b 100644 --- a/src/icons/blockquote-card.svg +++ b/src/icons/blockquote-card.svg @@ -1 +1,3 @@ - + + abc + diff --git a/src/icons/blockquote-simple.svg b/src/icons/blockquote-simple.svg index 51366f8fc..f248cfabe 100644 --- a/src/icons/blockquote-simple.svg +++ b/src/icons/blockquote-simple.svg @@ -1 +1,3 @@ - + + abc + diff --git a/src/theme/ItaliaTheme/_main.scss b/src/theme/ItaliaTheme/_main.scss index 762b0c360..93139d533 100644 --- a/src/theme/ItaliaTheme/_main.scss +++ b/src/theme/ItaliaTheme/_main.scss @@ -166,6 +166,40 @@ iframe { } } +//slate LINK +.inline-link { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.slate-editor-link { + margin: 0 0.5rem; + clear: both; + + &.btn { + display: inline-block; + padding: 12px 24px; + border-radius: $btn-border-radius; + font-weight: 600; + text-align: center; + text-decoration: none; + &:hover, + &:active { + font-weight: 600; + } + + &.btn-primary { + background-color: $primary; + color: $primary-text; + &:hover, + &:active { + background-color: darken($primary, 8); + color: $primary-text; + } + } + } +} + #text-body { .full-width { position: static; From 3f04f5c31c6e298c40d70ba5b645e765ad71856a Mon Sep 17 00:00:00 2001 From: Giulia Ghisini Date: Tue, 3 Oct 2023 16:18:19 +0200 Subject: [PATCH 007/353] feat: added Callout block --- .../ItaliaTheme/Blocks/Callout/Edit.jsx | 143 ++++++++ .../ItaliaTheme/Blocks/Callout/Sidebar.jsx | 135 ++++++++ .../ItaliaTheme/Blocks/Callout/View.jsx | 44 +++ .../manage/Widgets/ColorListWidget.jsx | 13 +- .../Widgets/TextEditorDraftJSWidget.jsx | 299 ++++++++++++++++ .../manage/Widgets/TextEditorWidget.jsx | 319 +++--------------- src/config/Blocks/ListingOptions/index.js | 1 - src/config/Blocks/ListingOptions/utils.js | 7 - src/config/Blocks/blocks.js | 28 +- src/config/Blocks/listingVariations.js | 2 +- .../components/LinkButton/index.js | 3 +- src/config/Slate/Alignment/AlignMenu.jsx | 2 +- .../Slate/Blockquote/BlockquoteMenu.jsx | 3 +- src/config/Slate/Link/index.js | 108 ++++++ src/config/Slate/Link/renderer.jsx | 19 +- src/config/Slate/TextLarger/index.js | 51 +++ src/config/Slate/config.js | 5 + src/config/Slate/dropdownUtils.js | 48 ++- src/config/italiaConfig.js | 16 - .../components/LinkButton/AddLinkForm.jsx | 213 +++++++----- .../components/manage/Form/BlocksToolbar.jsx | 2 +- src/helpers/blocks.js | 7 + src/icons/blocco-icone.svg | 3 - src/icons/blockquote-card-dark.svg | 4 +- src/icons/blockquote-card.svg | 4 +- src/icons/blockquote-simple.svg | 4 +- src/icons/text-larger.svg | 1 + src/theme/ItaliaTheme/Blocks/_callout.scss | 29 ++ src/theme/ItaliaTheme/_main.scss | 31 +- src/theme/_cms-ui.scss | 22 ++ src/theme/site.scss | 1 + 31 files changed, 1115 insertions(+), 452 deletions(-) create mode 100644 src/components/ItaliaTheme/Blocks/Callout/Edit.jsx create mode 100644 src/components/ItaliaTheme/Blocks/Callout/Sidebar.jsx create mode 100644 src/components/ItaliaTheme/Blocks/Callout/View.jsx create mode 100644 src/components/ItaliaTheme/manage/Widgets/TextEditorDraftJSWidget.jsx create mode 100644 src/config/Slate/Link/index.js create mode 100644 src/config/Slate/TextLarger/index.js rename src/{config/RichTextEditor/Plugins => customizations/volto/components/manage}/AnchorPlugin/components/LinkButton/AddLinkForm.jsx (51%) create mode 100644 src/helpers/blocks.js create mode 100644 src/icons/text-larger.svg create mode 100644 src/theme/ItaliaTheme/Blocks/_callout.scss diff --git a/src/components/ItaliaTheme/Blocks/Callout/Edit.jsx b/src/components/ItaliaTheme/Blocks/Callout/Edit.jsx new file mode 100644 index 000000000..dd30860b8 --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Callout/Edit.jsx @@ -0,0 +1,143 @@ +/** + * Edit Callout block. + * @module components/ItaliaTheme/Blocks/Callout/Edit + */ + +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, useIntl } from 'react-intl'; +import { isEqual } from 'lodash'; +import { SidebarPortal } from '@plone/volto/components'; + +import { Callout, CalloutTitle, CalloutText } from 'design-react-kit'; +import { Icon } from 'design-comuni-plone-theme/components/ItaliaTheme'; +import Sidebar from './Sidebar.jsx'; +import { TextEditorWidget } from 'design-comuni-plone-theme/components/ItaliaTheme'; + +const messages = defineMessages({ + title: { + id: 'Type the title…', + defaultMessage: 'Type the title…', + }, + text: { + id: 'Type text…', + defaultMessage: 'Digita il testo…', + }, +}); +/** + * Edit Calloout block class. + * @class Edit + * @extends Component + */ +const Edit = ({ + data, + onChangeBlock, + block, + onSelectBlock, + onAddBlock, + index, + selected, + onFocusPreviousBlock, + onFocusNextBlock, +}) => { + const intl = useIntl(); + const [selectedField, setSelectedField] = useState('title'); + + useEffect(() => { + if (selected) { + setSelectedField('title'); + } else { + setSelectedField(null); + } + }, [selected]); + /** + * Change handler + * @method onChange + * @param {object} editorState Editor state. + * @returns {undefined} + */ + const onChange = (obj, fieldname) => { + if (!isEqual(obj[fieldname], data[fieldname])) { + onChangeBlock(block, { + ...data, + [fieldname]: obj[fieldname], + }); + } + }; + + return __SERVER__ ? ( +
+ ) : ( +
+ + + + + setSelectedField('title')} + onSelectBlock={(block) => setSelectedField('title')} + onChangeBlock={(block, data) => { + onChange({ ...data, title: data.plaintext }, 'title'); + }} + selected={selectedField === 'title'} + placeholder={intl.formatMessage(messages.title)} + showToolbar={false} + index={index} + wrapClass={`title-edit-wrapper ${ + data.title?.blocks?.[0]?.text?.length > 0 ? 'has-text' : '' + }`} + // onAddBlock={() => { + // setSelectedField('text'); + // }} + // onFocusNextBlock={() => { + // setSelectedField('text'); + // }} + // onFocusPreviousBlock={onFocusPreviousBlock} + /> + + + setSelectedField('text')} + onSelectBlock={(block) => setSelectedField('text')} + onChangeBlock={(block, data) => { + onChange({ ...data, title: data.value }, 'text'); + }} + selected={selectedField === 'text'} + placeholder={intl.formatMessage(messages.text)} + index={index} + // onFocusNextBlock={onFocusNextBlock} + // onFocusPreviousBlock={() => { + // setSelectedField('title'); + // }} + /> + + + + + + +
+ ); +}; + +Edit.propTypes = { + properties: PropTypes.objectOf(PropTypes.any).isRequired, + selected: PropTypes.bool.isRequired, + index: PropTypes.number.isRequired, + onChangeField: PropTypes.func.isRequired, + onSelectBlock: PropTypes.func.isRequired, + onDeleteBlock: PropTypes.func.isRequired, + onAddBlock: PropTypes.func.isRequired, + onFocusPreviousBlock: PropTypes.func.isRequired, + onFocusNextBlock: PropTypes.func.isRequired, + block: PropTypes.string.isRequired, +}; + +export default Edit; diff --git a/src/components/ItaliaTheme/Blocks/Callout/Sidebar.jsx b/src/components/ItaliaTheme/Blocks/Callout/Sidebar.jsx new file mode 100644 index 000000000..da755c1bd --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Callout/Sidebar.jsx @@ -0,0 +1,135 @@ +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { Segment } from 'semantic-ui-react'; +import { FormattedMessage, injectIntl } from 'react-intl'; +import { /*TextWidget,*/ SelectWidget } from '@plone/volto/components'; + +import { defineMessages, useIntl } from 'react-intl'; +import IconWidget from 'design-comuni-plone-theme/components/ItaliaTheme/manage/Widgets/IconWidget'; +import { defaultIconWidgetOptions } from 'design-comuni-plone-theme/helpers/index'; +import { ColorListWidget } from 'design-comuni-plone-theme/components/ItaliaTheme'; + +const messages = defineMessages({ + color: { id: 'color', defaultMessage: 'Colore' }, + color_default: { + id: 'color_default', + defaultMessage: 'Default', + }, + color_success: { + id: 'color_success', + defaultMessage: 'Success', + }, + color_warning: { + id: 'color_warning', + defaultMessage: 'Warning', + }, + color_danger: { + id: 'color_danger', + defaultMessage: 'Danger', + }, + color_note: { + id: 'color_note', + defaultMessage: 'Note', + }, + style: { id: 'callout_style', defaultMessage: 'Stile' }, + style_basic: { id: 'callout_style_basic', defaultMessage: 'Base' }, + style_highlight: { + id: 'callout_style_highlight', + defaultMessage: 'In evidenza', + }, +}); + +const Sidebar = ({ data, block, onChangeBlock }) => { + const intl = useIntl(); + const colors = [ + { + name: 'callout_default', + label: intl.formatMessage(messages.color_default), + }, + { name: 'success', label: intl.formatMessage(messages.color_success) }, + { name: 'warning', label: intl.formatMessage(messages.color_warning) }, + { name: 'danger', label: intl.formatMessage(messages.color_danger) }, + { name: 'callout_note', label: intl.formatMessage(messages.color_note) }, + ]; + + const styles = [ + ['base', intl.formatMessage(messages.style_basic)], + ['highlight', intl.formatMessage(messages.style_highlight)], + ]; + + useEffect(() => { + if (!data.style) { + onChangeBlock(block, { ...data, style: styles[0][0] }); + } + }, []); + + return ( + +
+

+ +

+
+
+ { + onChangeBlock(block, { ...data, [id]: value }); + }} + choices={styles} + noValueOption={false} + /> + { + onChangeBlock(block, { + ...data, + [id]: value, + }); + }} + colors={colors} + /> + + { + onChangeBlock(block, { + ...data, + [name]: value, + }); + }} + /> + + {/* { + onChangeBlock(block, { + ...data, + linkMoreTitle: value, + }); + }} + /> */} +
+
+ ); +}; + +Sidebar.propTypes = { + data: PropTypes.objectOf(PropTypes.any), + block: PropTypes.string, + onChangeBlock: PropTypes.func, + selected: PropTypes.any, + setSelected: PropTypes.func, +}; + +export default injectIntl(Sidebar); diff --git a/src/components/ItaliaTheme/Blocks/Callout/View.jsx b/src/components/ItaliaTheme/Blocks/Callout/View.jsx new file mode 100644 index 000000000..96a14109b --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Callout/View.jsx @@ -0,0 +1,44 @@ +/** + * View Callout block. + * @module components/ItaliaTheme/Blocks/Callout/View + */ + +import React from 'react'; +import PropTypes from 'prop-types'; + +import { Callout, CalloutTitle, CalloutText } from 'design-react-kit'; +import { Icon } from 'design-comuni-plone-theme/components/ItaliaTheme'; +/** + * View Callout block class. + * @class CalloutView + * @extends Component + */ + +const View = ({ data, id }) => { + return ( +
+ + + + {data.title} + {data.title} + + + Maecenas vulputate ante dictum vestibulum volutpat. Lorem ipsum dolor + sit amet, consectetur adipiscing elit. Aenean non augue non purus + vestibulum varius. + + +
+ ); +}; +/** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ +View.propTypes = { + data: PropTypes.objectOf(PropTypes.any).isRequired, +}; + +export default View; diff --git a/src/components/ItaliaTheme/manage/Widgets/ColorListWidget.jsx b/src/components/ItaliaTheme/manage/Widgets/ColorListWidget.jsx index 34c857ab0..593b944f1 100644 --- a/src/components/ItaliaTheme/manage/Widgets/ColorListWidget.jsx +++ b/src/components/ItaliaTheme/manage/Widgets/ColorListWidget.jsx @@ -44,8 +44,16 @@ class ColorListWidget extends Component { * @returns {string} Markup for the component. */ render() { - const { id, title, required, value, onChange, intl, colors, className } = - this.props; + const { + id, + title, + required, + value, + onChange, + intl, + colors, + className, + } = this.props; return colors.length > 0 ? ( ); })} diff --git a/src/components/ItaliaTheme/manage/Widgets/TextEditorDraftJSWidget.jsx b/src/components/ItaliaTheme/manage/Widgets/TextEditorDraftJSWidget.jsx new file mode 100644 index 000000000..3baf8e730 --- /dev/null +++ b/src/components/ItaliaTheme/manage/Widgets/TextEditorDraftJSWidget.jsx @@ -0,0 +1,299 @@ +/** + * Edit text block. + * @module components/Widgets/TextEditorDraftJSWidget/TextEditorDraftJSWidget + */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { compose } from 'redux'; +import { defineMessages, injectIntl } from 'react-intl'; +import { includes, isEqual } from 'lodash'; +import loadable from '@loadable/component'; +import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; + +import config from '@plone/volto/registry'; + +const messages = defineMessages({ + text: { + id: 'Type text…', + defaultMessage: 'Type text…', + }, +}); + +const Editor = loadable(() => import('draft-js-plugins-editor')); + +/** + * TextEditorDraftJSWidget class. + * @class Edit + * @extends Component + */ +class TextEditorDraftJSWidgetComponent extends Component { + /** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ + static propTypes = { + data: PropTypes.objectOf(PropTypes.any).isRequired, + fieldName: PropTypes.string.isRequired, + selected: PropTypes.bool.isRequired, + block: PropTypes.string.isRequired, + onChangeBlock: PropTypes.func.isRequired, + placeholder: PropTypes.string, + focusOn: PropTypes.func, + nextFocus: PropTypes.any, + prevFocus: PropTypes.any, + onFocusNextBlock: PropTypes.any, + onFocusPreviousBlock: PropTypes.any, + showToolbar: PropTypes.bool, + onSelectBlock: PropTypes.func, + onAddBlock: PropTypes.func, + disableMoveToNearest: PropTypes.bool, + }; + + /** + * Default properties + * @property {Object} defaultProps Default properties. + * @static + */ + static defaultProps = { + showToolbar: true, + }; + + /** + * Constructor + * @method constructor + * @param {Object} props Component properties + * @constructs WysiwygEditor + */ + constructor(props) { + super(props); + const { settings } = config; + + this.draftConfig = settings.richtextEditorSettings(props); + + const { EditorState, convertFromRaw } = props.draftJs; + const createInlineToolbarPlugin = props.draftJsInlineToolbarPlugin.default; + + if (!__SERVER__) { + let editorState; + if (props.data && props.data[props.fieldName]) { + editorState = EditorState.createWithContent( + convertFromRaw(props.data[props.fieldName]), + ); + } else { + editorState = EditorState.createEmpty(); + } + + const inlineToolbarPlugin = createInlineToolbarPlugin({ + structure: this.draftConfig.richTextEditorInlineToolbarButtons, + }); + + this.state = { + editorState, + inlineToolbarPlugin, + addNewBlockOpened: false, + }; + } + } + + /** + * Component will receive props + * @method componentDidMount + * @returns {undefined} + */ + componentDidMount() { + if (this.props.selected && this.node) { + setTimeout(this.node.focus, 0); + } + } + + /** + * Component will receive props + * @method componentWillReceiveProps + * @param {Object} nextProps Next properties + * @returns {undefined} + */ + UNSAFE_componentWillReceiveProps(nextProps) { + if (!this.props.selected && nextProps.selected) { + // See https://github.com/draft-js-plugins/draft-js-plugins/issues/800 + setTimeout(this.node.focus, 0); + const { EditorState } = this.props.draftJs; + + this.setState({ + editorState: EditorState.moveFocusToEnd(this.state.editorState), + }); + } + } + + /** + * Change handler + * @method onChange + * @param {object} editorState Editor state. + * @returns {undefined} + */ + onChange = (editorState) => { + const { convertToRaw } = this.props.draftJs; + if ( + !isEqual( + convertToRaw(editorState.getCurrentContent()), + convertToRaw(this.state.editorState.getCurrentContent()), + ) + ) { + this.props.onChangeBlock({ + ...this.props.data, + [this.props.fieldName]: convertToRaw(editorState.getCurrentContent()), + }); + } + this.setState({ editorState }); + }; + + /** + * Render method. + * @method render + * @returns {string} Markup for the component. + */ + render() { + if (__SERVER__) { + return
; + } + + const { InlineToolbar } = this.state.inlineToolbarPlugin; + let placeholder = this.props.placeholder + ? this.props.placeholder + : this.props.intl.formatMessage(messages.text); + let disableMoveToNearest = this.props.disableMoveToNearest; + const isSoftNewlineEvent = this.props.draftJsLibIsSoftNewlineEvent.default; + const { RichUtils } = this.props.draftJs; + + return ( + <> +
+ { + // if (disableMoveToNearest) { + // e.stopPropagation(); + // } + if (isSoftNewlineEvent(e)) { + this.onChange( + RichUtils.insertSoftNewline(this.state.editorState), + ); + return 'handled'; + } + + if ( + !disableMoveToNearest && + this.props.onSelectBlock && + this.props.onAddBlock + ) { + const selectionState = this.state.editorState.getSelection(); + const anchorKey = selectionState.getAnchorKey(); + const currentContent = this.state.editorState.getCurrentContent(); + const currentContentBlock = currentContent.getBlockForKey( + anchorKey, + ); + const blockType = currentContentBlock.getType(); + if (!includes(this.draftConfig.listBlockTypes, blockType)) { + this.props.onSelectBlock( + this.props.onAddBlock('text', this.props.index + 1), + ); + return 'handled'; + } + return 'un-handled'; + } + + return {}; + }} + onUpArrow={(e) => { + if (this.props.prevFocus) { + this.props.setFocus(this.props.prevFocus); + e.stopPropagation(); + } else { + if (this.props.disableMoveToNearest) { + e.stopPropagation(); + } else { + if (this.props.onFocusPreviousBlock) { + const selectionState = this.state.editorState.getSelection(); + const currentCursorPosition = selectionState.getStartOffset(); + + if (currentCursorPosition === 0) { + this.props.onFocusPreviousBlock( + this.props.block, + this.node, + ); + } + } + } + } + }} + onDownArrow={(e) => { + if (this.props.nextFocus) { + this.props.setFocus(this.props.nextFocus); + e.stopPropagation(); + } else { + if (this.props.disableMoveToNearest) { + e.stopPropagation(); + } else { + if (this.props.onFocusNextBlock) { + const selectionState = this.state.editorState.getSelection(); + const { editorState } = this.state; + const currentCursorPosition = selectionState.getStartOffset(); + const blockLength = editorState + .getCurrentContent() + .getFirstBlock() + .getLength(); + + if (currentCursorPosition === blockLength) { + this.props.onFocusNextBlock(this.props.block, this.node); + } + } + } + } + }} + ref={(node) => { + this.node = node; + }} + /> + {this.props.showToolbar && this.node && } +
+ + ); + } +} + +export const TextEditorDraftJSWidget = React.memo( + compose( + injectIntl, + injectLazyLibs([ + 'draftJs', + 'draftJsLibIsSoftNewlineEvent', + 'draftJsFilters', + 'draftJsInlineToolbarPlugin', + 'draftJsBlockBreakoutPlugin', + 'draftJsCreateInlineStyleButton', + 'draftJsCreateBlockStyleButton', + 'immutableLib', + // TODO: add all plugin dependencies, also in Wysiwyg and Cell + ]), + )(TextEditorDraftJSWidgetComponent), +); + +const Preloader = (props) => { + const [loaded, setLoaded] = React.useState(false); + React.useEffect(() => { + Editor.load().then(() => setLoaded(true)); + }, []); + return loaded ? : null; +}; + +export default Preloader; diff --git a/src/components/ItaliaTheme/manage/Widgets/TextEditorWidget.jsx b/src/components/ItaliaTheme/manage/Widgets/TextEditorWidget.jsx index 0707c3cb8..27687d353 100644 --- a/src/components/ItaliaTheme/manage/Widgets/TextEditorWidget.jsx +++ b/src/components/ItaliaTheme/manage/Widgets/TextEditorWidget.jsx @@ -3,15 +3,13 @@ * @module components/Widgets/TextEditorWidget/TextEditorWidget */ -import React, { Component } from 'react'; +import React from 'react'; +import { defineMessages } from 'react-intl'; import PropTypes from 'prop-types'; -import { compose } from 'redux'; -import { defineMessages, injectIntl } from 'react-intl'; -import { includes, isEqual } from 'lodash'; -import loadable from '@loadable/component'; -import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; import config from '@plone/volto/registry'; +import { TextEditorDraftJSWidget } from './TextEditorDraftJSWidget'; +import { TextBlockEdit } from '@plone/volto-slate/Blocks/Text'; const messages = defineMessages({ text: { @@ -20,280 +18,41 @@ const messages = defineMessages({ }, }); -const Editor = loadable(() => import('draft-js-plugins-editor')); - -/** - * TextEditorWidget class. - * @class Edit - * @extends Component - */ -class TextEditorWidgetComponent extends Component { - /** - * Property types. - * @property {Object} propTypes Property types. - * @static - */ - static propTypes = { - data: PropTypes.objectOf(PropTypes.any).isRequired, - fieldName: PropTypes.string.isRequired, - selected: PropTypes.bool.isRequired, - block: PropTypes.string.isRequired, - onChangeBlock: PropTypes.func.isRequired, - placeholder: PropTypes.string, - focusOn: PropTypes.func, - nextFocus: PropTypes.any, - prevFocus: PropTypes.any, - onFocusNextBlock: PropTypes.any, - onFocusPreviousBlock: PropTypes.any, - showToolbar: PropTypes.bool, - onSelectBlock: PropTypes.func, - onAddBlock: PropTypes.func, - disableMoveToNearest: PropTypes.bool, - }; - - /** - * Default properties - * @property {Object} defaultProps Default properties. - * @static - */ - static defaultProps = { - showToolbar: true, - }; - - /** - * Constructor - * @method constructor - * @param {Object} props Component properties - * @constructs WysiwygEditor - */ - constructor(props) { - super(props); - const { settings } = config; - - this.draftConfig = settings.richtextEditorSettings(props); - - const { EditorState, convertFromRaw } = props.draftJs; - const createInlineToolbarPlugin = props.draftJsInlineToolbarPlugin.default; - - if (!__SERVER__) { - let editorState; - if (props.data && props.data[props.fieldName]) { - editorState = EditorState.createWithContent( - convertFromRaw(props.data[props.fieldName]), - ); - } else { - editorState = EditorState.createEmpty(); - } - - const inlineToolbarPlugin = createInlineToolbarPlugin({ - structure: this.draftConfig.richTextEditorInlineToolbarButtons, - }); - - this.state = { - editorState, - inlineToolbarPlugin, - addNewBlockOpened: false, - }; - } - } - - /** - * Component will receive props - * @method componentDidMount - * @returns {undefined} - */ - componentDidMount() { - if (this.props.selected && this.node) { - setTimeout(this.node.focus, 0); - } - } - - /** - * Component will receive props - * @method componentWillReceiveProps - * @param {Object} nextProps Next properties - * @returns {undefined} - */ - UNSAFE_componentWillReceiveProps(nextProps) { - if (!this.props.selected && nextProps.selected) { - // See https://github.com/draft-js-plugins/draft-js-plugins/issues/800 - setTimeout(this.node.focus, 0); - const { EditorState } = this.props.draftJs; - - this.setState({ - editorState: EditorState.moveFocusToEnd(this.state.editorState), - }); - } - } - - /** - * Change handler - * @method onChange - * @param {object} editorState Editor state. - * @returns {undefined} - */ - onChange = (editorState) => { - const { convertToRaw } = this.props.draftJs; - if ( - !isEqual( - convertToRaw(editorState.getCurrentContent()), - convertToRaw(this.state.editorState.getCurrentContent()), - ) - ) { - this.props.onChangeBlock({ - ...this.props.data, - [this.props.fieldName]: convertToRaw(editorState.getCurrentContent()), - }); - } - this.setState({ editorState }); - }; - - /** - * Render method. - * @method render - * @returns {string} Markup for the component. - */ - render() { - if (__SERVER__) { - return
; - } - - const { InlineToolbar } = this.state.inlineToolbarPlugin; - let placeholder = this.props.placeholder - ? this.props.placeholder - : this.props.intl.formatMessage(messages.text); - let disableMoveToNearest = this.props.disableMoveToNearest; - const isSoftNewlineEvent = this.props.draftJsLibIsSoftNewlineEvent.default; - const { RichUtils } = this.props.draftJs; - - return ( - <> -
- { - // if (disableMoveToNearest) { - // e.stopPropagation(); - // } - if (isSoftNewlineEvent(e)) { - this.onChange( - RichUtils.insertSoftNewline(this.state.editorState), - ); - return 'handled'; - } - - if ( - !disableMoveToNearest && - this.props.onSelectBlock && - this.props.onAddBlock - ) { - const selectionState = this.state.editorState.getSelection(); - const anchorKey = selectionState.getAnchorKey(); - const currentContent = this.state.editorState.getCurrentContent(); - const currentContentBlock = currentContent.getBlockForKey( - anchorKey, - ); - const blockType = currentContentBlock.getType(); - if (!includes(this.draftConfig.listBlockTypes, blockType)) { - this.props.onSelectBlock( - this.props.onAddBlock('text', this.props.index + 1), - ); - return 'handled'; - } - return 'un-handled'; - } - - return {}; - }} - onUpArrow={(e) => { - if (this.props.prevFocus) { - this.props.setFocus(this.props.prevFocus); - e.stopPropagation(); - } else { - if (this.props.disableMoveToNearest) { - e.stopPropagation(); - } else { - if (this.props.onFocusPreviousBlock) { - const selectionState = this.state.editorState.getSelection(); - const currentCursorPosition = selectionState.getStartOffset(); - - if (currentCursorPosition === 0) { - this.props.onFocusPreviousBlock( - this.props.block, - this.node, - ); - } - } - } - } - }} - onDownArrow={(e) => { - if (this.props.nextFocus) { - this.props.setFocus(this.props.nextFocus); - e.stopPropagation(); - } else { - if (this.props.disableMoveToNearest) { - e.stopPropagation(); - } else { - if (this.props.onFocusNextBlock) { - const selectionState = this.state.editorState.getSelection(); - const { editorState } = this.state; - const currentCursorPosition = selectionState.getStartOffset(); - const blockLength = editorState - .getCurrentContent() - .getFirstBlock() - .getLength(); - - if (currentCursorPosition === blockLength) { - this.props.onFocusNextBlock(this.props.block, this.node); - } - } - } - } - }} - ref={(node) => { - this.node = node; - }} - /> - {this.props.showToolbar && this.node && } -
- - ); - } -} - -export const TextEditorWidget = React.memo( - compose( - injectIntl, - injectLazyLibs([ - 'draftJs', - 'draftJsLibIsSoftNewlineEvent', - 'draftJsFilters', - 'draftJsInlineToolbarPlugin', - 'draftJsBlockBreakoutPlugin', - 'draftJsCreateInlineStyleButton', - 'draftJsCreateBlockStyleButton', - 'immutableLib', - // TODO: add all plugin dependencies, also in Wysiwyg and Cell - ]), - )(TextEditorWidgetComponent), -); +//[ToDo]: se togliamo completamente draftjs, togliere la prop editor_type, la condizione e il widget di TextEditorDraftJSWidget + +const TextEditorWidget = ({ + editor_type = 'slate', + showToolbar = true, + setSelected, + wrapClass, + ...props +}) => { + return editor_type === 'slate' ? ( +
setSelected()} + onFocus={() => setSelected()} + onKeyDown={() => setSelected()} + role="textbox" + tabIndex="-1" + > + +
+ ) : ( + + ); +}; -const Preloader = (props) => { - const [loaded, setLoaded] = React.useState(false); - React.useEffect(() => { - Editor.load().then(() => setLoaded(true)); - }, []); - return loaded ? : null; +TextEditorWidget.propTypes = { + editor_type: PropTypes.string.isRequired, + data: PropTypes.object.isRequired, + setSelected: PropTypes.func.isRequired, + onSelectBlock: PropTypes.func.isRequired, + onChangeBlock: PropTypes.func.isRequired, + block: PropTypes.string.isRequired, + selected: PropTypes.bool.isRequired, + showToolbar: PropTypes.bool, + wrapClass: PropTypes.string, }; -export default Preloader; +export default TextEditorWidget; diff --git a/src/config/Blocks/ListingOptions/index.js b/src/config/Blocks/ListingOptions/index.js index caab64e02..691a80ff3 100644 --- a/src/config/Blocks/ListingOptions/index.js +++ b/src/config/Blocks/ListingOptions/index.js @@ -1,7 +1,6 @@ export { addSchemaField, templatesOptions, - cloneBlock, } from 'design-comuni-plone-theme/config/Blocks/ListingOptions/utils'; export addDefaultOptions from 'design-comuni-plone-theme/config/Blocks/ListingOptions/defaultOptions'; export addDefaultAdditionalOptions from 'design-comuni-plone-theme/config/Blocks/ListingOptions/defaultAdditionalOptions'; diff --git a/src/config/Blocks/ListingOptions/utils.js b/src/config/Blocks/ListingOptions/utils.js index 2e13dfbd9..b9eb9f7d9 100644 --- a/src/config/Blocks/ListingOptions/utils.js +++ b/src/config/Blocks/ListingOptions/utils.js @@ -1,5 +1,4 @@ import { defineMessages } from 'react-intl'; -import { v4 as uuid } from 'uuid'; const messages = defineMessages({ show_icon: { @@ -180,9 +179,3 @@ export const addLighthouseField = (schema, intl, position = 0) => { return pos; }; - -export const cloneBlock = (blockData) => { - const blockID = uuid(); - const clonedData = { ...blockData, block: blockID }; - return [blockID, clonedData]; -}; diff --git a/src/config/Blocks/blocks.js b/src/config/Blocks/blocks.js index a1e8d0f14..76ca118e5 100644 --- a/src/config/Blocks/blocks.js +++ b/src/config/Blocks/blocks.js @@ -65,7 +65,11 @@ import countDownSVG from 'design-comuni-plone-theme/icons/count-down.svg'; import CountDownBlockView from 'design-comuni-plone-theme/components/ItaliaTheme/Blocks/CountDown/View'; import CountDownBlockEdit from 'design-comuni-plone-theme/components/ItaliaTheme/Blocks/CountDown/Edit'; -import { cloneBlock } from 'design-comuni-plone-theme/config/Blocks/ListingOptions'; +import calloutSVG from '@plone/volto/icons/megaphone.svg'; +import CalloutView from 'design-comuni-plone-theme/components/ItaliaTheme/Blocks/Callout/View'; +import CalloutEdit from 'design-comuni-plone-theme/components/ItaliaTheme/Blocks/Callout/Edit'; + +import { cloneBlock } from 'design-comuni-plone-theme/helpers/blocks'; const italiaBlocks = { highlitedContent: { @@ -360,13 +364,29 @@ const italiaBlocks = { }, sidebarTab: 1, }, + callout_block: { + id: 'callout_block', + title: 'Callout', + icon: calloutSVG, + group: 'text', + view: CalloutView, + edit: CalloutEdit, + restricted: false, + mostUsed: false, + cloneData: cloneBlock, + security: { + addPermission: [], + view: [], + }, + sidebarTab: 1, + }, }; const getItaliaBlocks = (config) => { delete config.blocks.blocksConfig.teaser; - config.blocks.blocksConfig.gridBlock.allowedBlocks = config.blocks.blocksConfig.gridBlock.allowedBlocks - .filter((item) => !['slate', 'teaser'].includes(item)) - .concat(['text']); + config.blocks.blocksConfig.gridBlock.allowedBlocks = config.blocks.blocksConfig.gridBlock.allowedBlocks.filter( + (item) => !['teaser'].includes(item), + ); return italiaBlocks; }; export default getItaliaBlocks; diff --git a/src/config/Blocks/listingVariations.js b/src/config/Blocks/listingVariations.js index 0be7c3197..fe6492cd3 100644 --- a/src/config/Blocks/listingVariations.js +++ b/src/config/Blocks/listingVariations.js @@ -60,8 +60,8 @@ import { addPhotogalleryTemplateOptions, addLinkMoreOptions, addSmallBlockLinksTemplateOptions, - cloneBlock, } from 'design-comuni-plone-theme/config/Blocks/ListingOptions'; +import { cloneBlock } from 'design-comuni-plone-theme/helpers/blocks'; import { addLighthouseField } from 'design-comuni-plone-theme/config/Blocks/ListingOptions/utils'; diff --git a/src/config/RichTextEditor/Plugins/AnchorPlugin/components/LinkButton/index.js b/src/config/RichTextEditor/Plugins/AnchorPlugin/components/LinkButton/index.js index d416a4d94..c05ceb38e 100644 --- a/src/config/RichTextEditor/Plugins/AnchorPlugin/components/LinkButton/index.js +++ b/src/config/RichTextEditor/Plugins/AnchorPlugin/components/LinkButton/index.js @@ -10,8 +10,7 @@ import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; import EditorUtils from '@plone/volto/components/manage/AnchorPlugin/utils/EditorUtils'; import Icon from '@plone/volto/components/theme/Icon/Icon'; -import AddLinkForm from 'design-comuni-plone-theme/config/RichTextEditor/Plugins/AnchorPlugin/components/LinkButton/AddLinkForm'; - +import AddLinkForm from '@plone/volto/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm'; //è stata customizzata in customizations import linkSVG from '@plone/volto/icons/link.svg'; import unlinkSVG from '@plone/volto/icons/unlink.svg'; diff --git a/src/config/Slate/Alignment/AlignMenu.jsx b/src/config/Slate/Alignment/AlignMenu.jsx index 774460ebe..da6ed6359 100644 --- a/src/config/Slate/Alignment/AlignMenu.jsx +++ b/src/config/Slate/Alignment/AlignMenu.jsx @@ -61,7 +61,7 @@ const MenuOpts = ({ editor, toSelect, option, type }) => { as="span" active={isActive} className={cx(`${type}-${option.value}`, { active: isActive })} - {...omit(option, ['isBlock'])} + {...omit(option, ['isBlock', 'originalIcon'])} data-isblock={option.isBlock} onClick={(event, selItem) => { toggleStyle(editor, { diff --git a/src/config/Slate/Blockquote/BlockquoteMenu.jsx b/src/config/Slate/Blockquote/BlockquoteMenu.jsx index deae16b09..5f4afc16e 100644 --- a/src/config/Slate/Blockquote/BlockquoteMenu.jsx +++ b/src/config/Slate/Blockquote/BlockquoteMenu.jsx @@ -12,7 +12,6 @@ import { import { Icon } from '@plone/volto/components'; import { ToolbarButton } from '@plone/volto-slate/editor/ui'; -import quoteIcon from '@plone/volto/icons/quote.svg'; import blockquoteSimpleIcon from 'design-comuni-plone-theme/icons/blockquote-simple.svg'; import blockquoteCardIcon from 'design-comuni-plone-theme/icons/blockquote-card.svg'; import blockquoteCardDarkIcon from 'design-comuni-plone-theme/icons/blockquote-card-dark.svg'; @@ -59,7 +58,7 @@ const MenuOpts = ({ editor, toSelect, option, type, ...props }) => { as="span" active={isActive} className={cx(`${type}-${option.value}`, { active: isActive })} - {...omit(option, ['isBlock'])} + {...omit(option, ['isBlock', 'originalIcon'])} data-isblock={option.isBlock} onClick={(event, selItem) => { toggleStyle(editor, { diff --git a/src/config/Slate/Link/index.js b/src/config/Slate/Link/index.js new file mode 100644 index 000000000..0d25cbc91 --- /dev/null +++ b/src/config/Slate/Link/index.js @@ -0,0 +1,108 @@ +import React from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { ReactEditor } from 'slate-react'; +import { + _insertElement, + _unwrapElement, + _isActiveElement, + _getActiveElement, +} from '@plone/volto-slate/elementEditor/utils'; +import { SIMPLELINK } from '@plone/volto-slate/constants'; +import { useSelectionPosition } from '@plone/volto-slate/hooks'; +import { setPluginOptions } from '@plone/volto-slate/actions'; +import { PositionedToolbar } from '@plone/volto-slate/editor/ui'; +import AddLinkForm from '@plone/volto/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm'; + +const LinkEditor = (props) => { + const { + editor, + pluginId, + getActiveElement, + unwrapElement, + insertElement, + } = props; + const pid = `${editor.uid}-${pluginId}`; + const showEditor = useSelector((state) => { + return state['slate_plugins']?.[pid]?.show_sidebar_editor; + }); + const savedPosition = React.useRef(); + const rect = useSelectionPosition(); + + const dispatch = useDispatch(); + + const active = getActiveElement(editor); + // console.log('active', active); + const [node] = active || []; + + if (showEditor && !savedPosition.current) { + savedPosition.current = getPositionStyle(rect); + } + const dataElement = + node?.data?.dataElement || node?.data?.['data-element'] || ''; + + return showEditor ? ( + + { + if (!active) { + if (!editor.selection) editor.selection = editor.savedSelection; + insertElement(editor, { url, dataElement }); + } else { + const selection = unwrapElement(editor); + editor.selection = selection; + insertElement(editor, { url, dataElement }); + } + ReactEditor.focus(editor); + dispatch(setPluginOptions(pid, { show_sidebar_editor: false })); + savedPosition.current = null; + }} + onClear={() => { + // clear button was pressed in the link edit popup + const newSelection = JSON.parse( + JSON.stringify(unwrapElement(editor)), + ); + editor.selection = newSelection; + editor.savedSelection = newSelection; + }} + onOverrideContent={(c) => { + dispatch(setPluginOptions(pid, { show_sidebar_editor: false })); + savedPosition.current = null; + }} + /> + + ) : null; +}; +function getPositionStyle(rect) { + return { + style: { + opacity: 1, + top: rect.top + window.pageYOffset - 6, + left: rect.left + window.pageXOffset + rect.width / 2, + }, + }; +} + +export default function install(config) { + const { slate } = config.settings; + const PLUGINID = SIMPLELINK; + const insertElement = _insertElement(PLUGINID); + const getActiveElement = _getActiveElement(PLUGINID); + const isActiveElement = _isActiveElement(PLUGINID); + const unwrapElement = _unwrapElement(PLUGINID); + const pluginOptions = { + insertElement, + getActiveElement, + isActiveElement, + unwrapElement, + }; + + slate.persistentHelpers.push((props) => ( + + )); + + return config; +} diff --git a/src/config/Slate/Link/renderer.jsx b/src/config/Slate/Link/renderer.jsx index e44cd62ef..dcb7902eb 100644 --- a/src/config/Slate/Link/renderer.jsx +++ b/src/config/Slate/Link/renderer.jsx @@ -3,8 +3,19 @@ import { UniversalLink } from '@plone/volto/components'; import config from '@plone/volto/registry'; import { isInternalURL, flattenToAppURL } from '@plone/volto/helpers'; -const ViewLink = ({ url, target, download, children, className }) => { +const ViewLink = ({ + url, + target, + download, + children, + className, + dataElement, +}) => { const { openExternalLinkInNewTab } = config.settings; + let dataElementAttr = {}; + if (dataElement) { + dataElementAttr['data-element'] = dataElement; + } return ( { } download={download} className={className} + {...dataElementAttr} > {children} @@ -22,6 +34,10 @@ const ViewLink = ({ url, target, download, children, className }) => { export const LinkElement = (props) => { const { attributes, children, element, mode = 'edit' } = props; + let dataElementAttr = {}; + if (element.data.dataElement) { + dataElementAttr['data-element'] = element.data.dataElement; + } return mode === 'view' ? ( {children} @@ -29,6 +45,7 @@ export const LinkElement = (props) => { ) : ( { + return {children}; +}; + +const TextLargerButton = ({ icon, active, ...props }) => { + const CLASSNAME = 'text-larger'; + const editor = useSlate(); + const isActive = isInlineStyleActive(editor, CLASSNAME); + + return ( + + toggleStyle(editor, { cssClass: CLASSNAME, isBlock: false }) + } + /> + ); +}; + +export default function install(config) { + const { slate } = config.settings; + + slate.buttons.textLarger = (props) => ( + + ); + slate.elements.textLarger = TextLargerElement; + + //aggiungo il bottone di headings alla toolbar, dopo strikethrough + insertToolbarButtons(['textLarger'], 'strikethrough', slate); + + return config; +} diff --git a/src/config/Slate/config.js b/src/config/Slate/config.js index eee111d2d..d2de6fbc2 100644 --- a/src/config/Slate/config.js +++ b/src/config/Slate/config.js @@ -4,11 +4,16 @@ import installHeadings from 'design-comuni-plone-theme/config/Slate/Headings'; import installUnderline from 'design-comuni-plone-theme/config/Slate/Underline'; import installBlockquote from 'design-comuni-plone-theme/config/Slate/Blockquote'; import installLinkButton from 'design-comuni-plone-theme/config/Slate/LinkButton'; +import installTextLarger from 'design-comuni-plone-theme/config/Slate/TextLarger'; +import installLink from 'design-comuni-plone-theme/config/Slate/Link'; + export default function applyItaliaSlateConfig(config) { installAlignment(config); installHeadings(config); installUnderline(config); + installTextLarger(config); + installLink(config); installBlockquote(config); installLinkButton(config); diff --git a/src/config/Slate/dropdownUtils.js b/src/config/Slate/dropdownUtils.js index 05db4fd58..7803cfac9 100644 --- a/src/config/Slate/dropdownUtils.js +++ b/src/config/Slate/dropdownUtils.js @@ -32,22 +32,40 @@ export const toggleBlockStyle = ( const { slate } = config.settings; const isListItem = isBlockActive(editor, slate.listItemType); + const isBlockquote = isBlockActive(editor, 'blockquote'); const isActive = isBlockStyleActive(editor, style); const wantsList = false; - - if (isListItem && !wantsList) { - toggleBlockStyleAsListItem(editor, style); + const wantsBlockquote = format === 'blockquote'; + + if (isListItem && wantsBlockquote && !isBlockquote) { + //wrap list with blockquote + Transforms.wrapNodes( + editor, + { type: format }, + { + mode: 'highest', + split: false, + }, + ); + internalToggleBlockStyle(editor, style, oneOf, format); + } else if (isListItem && wantsBlockquote && isBlockquote) { + //remove blockquote wrap to list + //Transforms.unwrapNodes(editor, { mode: 'all', at: [0] }); + const unwrap = true; + internalToggleBlockStyle(editor, style, oneOf, format, unwrap); + } else if (isListItem && !wantsList) { + toggleBlockStyleAsListItem(editor, style, oneOf); } else if (isListItem && wantsList && !isActive) { // switchListType(editor, format); // this will deconstruct to Volto blocks } else if (!isListItem && wantsList) { // changeBlockToList(editor, format); } else if (!isListItem && !wantsList) { + //se ho rimosso tutti gli stili devo rimuoverre anche il format if (format && editor.children?.[0]?.type !== format) { toggleFormat(editor, format, allowedChildren); } internalToggleBlockStyle(editor, style, oneOf, format); - //sse ho rimosso tutti gli stili devo rimuoverre anche il format } else { console.warn('toggleBlockStyle case not covered, please examine:', { wantsList, @@ -198,8 +216,14 @@ export const isLinkStyleActive = (editor, style) => { return false; }; -export const internalToggleBlockStyle = (editor, style, oneOf, format) => { - toggleBlockStyleInSelection(editor, style, oneOf, format); +export const internalToggleBlockStyle = ( + editor, + style, + oneOf, + format, + unwrap, +) => { + return toggleBlockStyleInSelection(editor, style, oneOf, format, unwrap); }; export const internalToggleInlineStyle = (editor, style) => { @@ -210,8 +234,8 @@ export const internalToggleInlineStyle = (editor, style) => { * Applies a block format unto a list item. Will split the list and deconstruct the * block */ -export const toggleBlockStyleAsListItem = (editor, style) => { - toggleBlockStyleInSelection(editor, style); +export const toggleBlockStyleAsListItem = (editor, style, oneOf) => { + toggleBlockStyleInSelection(editor, style, oneOf); }; /* @@ -301,7 +325,7 @@ function toggleLinkStyleInSelection(editor, style, oneOf) { } } -function toggleBlockStyleInSelection(editor, style, oneOf, format) { +function toggleBlockStyleInSelection(editor, style, oneOf, format, unwrap) { const sn = Array.from( Editor.nodes(editor, { mode: 'highest', @@ -342,7 +366,11 @@ function toggleBlockStyleInSelection(editor, style, oneOf, format) { if (addStyle) { cn = cn.concat(style); } else if (format) { - toggleFormat(editor, format); + if (unwrap) { + Transforms.unwrapNodes(editor, { mode: 'all', at: [0] }); + } else { + toggleFormat(editor, format); + } } cn = cn.join(' '); } else if (n.styleName.split(' ').filter((x) => x === style).length > 0) { diff --git a/src/config/italiaConfig.js b/src/config/italiaConfig.js index 7f6612b77..0795e5fba 100644 --- a/src/config/italiaConfig.js +++ b/src/config/italiaConfig.js @@ -396,22 +396,6 @@ export default function applyConfig(voltoConfig) { sidebarTab: 1, }, rssBlock, - text: { - ...config.blocks.blocksConfig.text, - restricted: false, - }, - slate: { - ...config.blocks.blocksConfig.slate, - restricted: true, - }, - // table: { - // ...config.blocks.blocksConfig.table, - // restricted: false, - // }, - // slateTable: { - // ...config.blocks.blocksConfig.slateTable, - // restricted: true, - // }, maps: { ...config.blocks.blocksConfig.maps, restricted: true, diff --git a/src/config/RichTextEditor/Plugins/AnchorPlugin/components/LinkButton/AddLinkForm.jsx b/src/customizations/volto/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm.jsx similarity index 51% rename from src/config/RichTextEditor/Plugins/AnchorPlugin/components/LinkButton/AddLinkForm.jsx rename to src/customizations/volto/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm.jsx index 34f3eace2..bab4ec682 100644 --- a/src/config/RichTextEditor/Plugins/AnchorPlugin/components/LinkButton/AddLinkForm.jsx +++ b/src/customizations/volto/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm.jsx @@ -105,6 +105,12 @@ class AddLinkForm extends Component { } handleClickOutside = (e) => { + // console.log( + // 'handle', + // this.linkFormContainer.current, + // this.props, + // doesNodeContainClick(this.linkFormContainer.current, e), + // ); if ( this.linkFormContainer.current && doesNodeContainClick(this.linkFormContainer.current, e) @@ -243,103 +249,124 @@ class AddLinkForm extends Component { return (
- -
- {/* INPUT LINK */} -
- this.onChange(target.value)} - placeholder={this.props.intl.formatMessage( - messages.placeholder, +
+ + + + + + + +
+ {/* INPUT LINK */} +
+ this.onChange(target.value)} + placeholder={this.props.intl.formatMessage( + messages.placeholder, + )} + onKeyDown={this.onKeyDown} + ref={this.onRef} + /> + {value.length > 0 ? ( + + + + ) : ( + + + )} - onKeyDown={this.onKeyDown} - ref={this.onRef} - /> - {value.length > 0 ? ( - - - - ) : ( - - - - )} +
+ + {/* DATA-ELEMENT SELECT */} +
+ { + this.setState({ dataElement: value }); + }} + choices={this.selectOptions} + noValueOption={false} + wrapped={false} + /> +
- {/* DATA-ELEMENT SELECT */} -
- { - this.setState({ dataElement: value }); - }} - choices={this.selectOptions} - noValueOption={false} - wrapped={false} - /> + {/* BUTTON SUBMIT */} +
+ + +
-
- - {/* BUTTON SUBMIT */} -
- - - -
-
+ +
); } diff --git a/src/customizations/volto/components/manage/Form/BlocksToolbar.jsx b/src/customizations/volto/components/manage/Form/BlocksToolbar.jsx index 19a20152d..37a8bbe9c 100644 --- a/src/customizations/volto/components/manage/Form/BlocksToolbar.jsx +++ b/src/customizations/volto/components/manage/Form/BlocksToolbar.jsx @@ -17,7 +17,7 @@ import { Icon } from '@plone/volto/components'; import { Plug } from '@plone/volto/components/manage/Pluggable'; import { load } from 'redux-localstorage-simple'; import { isEqual, omit, without } from 'lodash'; -import { cloneBlock } from 'design-comuni-plone-theme/config/Blocks/ListingOptions'; +import { cloneBlock } from 'design-comuni-plone-theme/helpers/blocks'; import { setBlocksClipboard, resetBlocksClipboard } from '@plone/volto/actions'; import config from '@plone/volto/registry'; diff --git a/src/helpers/blocks.js b/src/helpers/blocks.js new file mode 100644 index 000000000..a02bd31e7 --- /dev/null +++ b/src/helpers/blocks.js @@ -0,0 +1,7 @@ +import { v4 as uuid } from 'uuid'; + +export const cloneBlock = (blockData) => { + const blockID = uuid(); + const clonedData = { ...blockData, block: blockID }; + return [blockID, clonedData]; +}; diff --git a/src/icons/blocco-icone.svg b/src/icons/blocco-icone.svg index fb0dcd62b..3ddeb7ef9 100644 --- a/src/icons/blocco-icone.svg +++ b/src/icons/blocco-icone.svg @@ -1,8 +1,5 @@ - - blocco-icone - Created with Sketch. diff --git a/src/icons/blockquote-card-dark.svg b/src/icons/blockquote-card-dark.svg index 781df1974..7a65411e4 100644 --- a/src/icons/blockquote-card-dark.svg +++ b/src/icons/blockquote-card-dark.svg @@ -1,3 +1 @@ - - abc - + diff --git a/src/icons/blockquote-card.svg b/src/icons/blockquote-card.svg index 4e62e534b..efa2cbbdf 100644 --- a/src/icons/blockquote-card.svg +++ b/src/icons/blockquote-card.svg @@ -1,3 +1 @@ - - abc - + diff --git a/src/icons/blockquote-simple.svg b/src/icons/blockquote-simple.svg index f248cfabe..decc0a84f 100644 --- a/src/icons/blockquote-simple.svg +++ b/src/icons/blockquote-simple.svg @@ -1,3 +1 @@ - - abc - + diff --git a/src/icons/text-larger.svg b/src/icons/text-larger.svg new file mode 100644 index 000000000..095e8d9d4 --- /dev/null +++ b/src/icons/text-larger.svg @@ -0,0 +1 @@ + diff --git a/src/theme/ItaliaTheme/Blocks/_callout.scss b/src/theme/ItaliaTheme/Blocks/_callout.scss new file mode 100644 index 000000000..8831fc5ba --- /dev/null +++ b/src/theme/ItaliaTheme/Blocks/_callout.scss @@ -0,0 +1,29 @@ +body.cms-ui { + .block-editor-callout_block { + .public-ui { + .callout { + .callout-title { + display: flex; + .icon { + flex: 1 0 100%; + max-width: 32px; + } + .title-edit-wrapper { + flex: 1 1 100%; + min-width: 165px; + &.has-text { + min-width: unset; + } + } + } + + p { + &, + * { + font-family: 'Lora', Georgia, serif !important; //as bootstrap-italia says + } + } + } + } + } +} diff --git a/src/theme/ItaliaTheme/_main.scss b/src/theme/ItaliaTheme/_main.scss index 93139d533..5bb29717d 100644 --- a/src/theme/ItaliaTheme/_main.scss +++ b/src/theme/ItaliaTheme/_main.scss @@ -90,28 +90,19 @@ iframe { .cms-ui { blockquote { border-color: $primary !important; - } - - .callout { - padding: 1.25rem; - border: 1px solid $neutral-1-a2; - border-left-width: 0.4rem; - margin: 1.25rem 0; - border-radius: 0.25rem; - p, - .public-DraftStyleDefault-block { - &:last-of-type { - margin-bottom: 0; - } + ul:first-child, + ol:first-child { + margin-top: 0; + } + ul:last-child, + ol:last-child { + margin-bottom: 0; } } - .callout-bg { - padding: 1.25rem; - margin: 1.25rem 0; - background-color: $primary-c1; - + .callout { + box-shadow: none; //override pastanaga styles p, .public-DraftStyleDefault-block { &:last-of-type { @@ -124,7 +115,9 @@ iframe { font-size: 0.75em; } - .draftjs-text-larger { + .draftjs-text-larger, + .text-larger { + //.text-larger: stile applicabile da editor Slate font-size: 1.75em; } diff --git a/src/theme/_cms-ui.scss b/src/theme/_cms-ui.scss index 9e5698382..583ecb701 100644 --- a/src/theme/_cms-ui.scss +++ b/src/theme/_cms-ui.scss @@ -145,6 +145,7 @@ body.cms-ui { color: #444; font-weight: normal; height: auto; + font-size: 0.7rem; } #field-data-element-select { @@ -362,6 +363,27 @@ body.cms-ui { .button.tertiary.active { background-color: $tertiary; } + .button.success, + .button.success.active { + background-color: $success; + } + .button.warning, + .button.success.warning { + background-color: $warning; + } + .button.danger, + .button.danger.active { + background-color: $danger; + } + + .button.callout_default, + .button.callout_default.active { + background-color: $secondary; + } + .button.callout_note, + .button.callout_note.active { + background-color: $primary; + } } .path-filter-widget { diff --git a/src/theme/site.scss b/src/theme/site.scss index 70e4fe563..354bd79f3 100644 --- a/src/theme/site.scss +++ b/src/theme/site.scss @@ -89,6 +89,7 @@ @import 'ItaliaTheme/Blocks/simpleCardTemplate'; @import 'ItaliaTheme/Blocks/search'; @import 'ItaliaTheme/Blocks/gridBlock'; +@import 'ItaliaTheme/Blocks/callout'; @import 'ItaliaTheme/Views/uo'; @import 'ItaliaTheme/Views/evento'; From 13cf384e5c41cb9f5741473b0ac6bda250e0ba7d Mon Sep 17 00:00:00 2001 From: Giulia Ghisini Date: Thu, 5 Oct 2023 11:10:09 +0200 Subject: [PATCH 008/353] breaking: removed draftjs in TextEditorWidget and used Slate, added SimpleTextEditorWidget for editing simple strings in blocks --- .../manage/Widgets/SimpleTextEditorWidget.jsx | 102 ++++++ .../Widgets/TextEditorDraftJSWidget.jsx | 299 ------------------ .../manage/Widgets/TextEditorWidget.jsx | 133 ++++++-- src/config/Blocks/blocks.js | 1 + src/config/Slate/config.js | 4 +- src/config/Slate/handlers.js | 58 ++++ 6 files changed, 277 insertions(+), 320 deletions(-) create mode 100644 src/components/ItaliaTheme/manage/Widgets/SimpleTextEditorWidget.jsx delete mode 100644 src/components/ItaliaTheme/manage/Widgets/TextEditorDraftJSWidget.jsx create mode 100644 src/config/Slate/handlers.js diff --git a/src/components/ItaliaTheme/manage/Widgets/SimpleTextEditorWidget.jsx b/src/components/ItaliaTheme/manage/Widgets/SimpleTextEditorWidget.jsx new file mode 100644 index 000000000..10b8e98b1 --- /dev/null +++ b/src/components/ItaliaTheme/manage/Widgets/SimpleTextEditorWidget.jsx @@ -0,0 +1,102 @@ +/** + * Edit simple text block. + * @module components/Widgets/SimpleTextEditorWidget/SimpleTextEditorWidget + * + * E' un editor di testo da mettere nei blocchi, senza formattazione. + */ + +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { defineMessages, useIntl } from 'react-intl'; +import { useInView } from 'react-intersection-observer'; + +import { handleKeyDetached } from '@plone/volto-slate/blocks/Text/keyboard'; +import { + uploadContent, + saveSlateBlockSelection, +} from '@plone/volto-slate/actions'; +import { commonSearchBlockMessages } from '../../../../helpers'; +import config from '@plone/volto/registry'; + +const messages = defineMessages({ + text: { + id: 'Type text…', + defaultMessage: 'Type text…', + }, +}); + +const SimpleTextEditorWidget = (props) => { + const intl = useIntl(); + const { + data, + setSelected, + onSelectBlock, + onChangeBlock, + block, + value, + selected, + placeholder, + } = props; + const { ref, inView } = useInView({ + threshold: 0, + rootMargin: '0px 0px 200px 0px', + }); + + const handleKey = (event) => { + const { slate } = config.settings; + + const handlers = slate.textblockDetachedKeyboardHandlers[event.key]; + + if (handlers) { + // a handler can return `true` to signify it has handled the event in this + // case, the execution flow is stopped + const handlerProps = { getBlockProps: () => props }; + return handlers.find((handler) => + handler({ editor: handlerProps, event }), + ); + } + }; + + return ( +
+