From e0d368edba56835348c1a433fc9d4ca4907a2944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Andrei?= Date: Sun, 25 Feb 2024 11:40:15 -0300 Subject: [PATCH] feat: Add Gist Block and Caption component --- package.json | 3 +- src/components/Blocks/Code/DefaultView.jsx | 3 ++ src/components/Blocks/Code/Edit.jsx | 4 +- src/components/Blocks/Code/schema.js | 20 ++++++++ src/components/Blocks/Gist/Data.jsx | 27 ++++++++++ src/components/Blocks/Gist/DefaultView.jsx | 15 ++++++ src/components/Blocks/Gist/Edit.jsx | 26 ++++++++++ src/components/Blocks/Gist/View.jsx | 15 ++++++ src/components/Blocks/Gist/schema.js | 59 ++++++++++++++++++++++ src/components/Caption/Caption.jsx | 39 ++++++++++++++ src/index.js | 45 +++++++++++++---- src/theme/main.less | 46 +++++++++++++++++ 12 files changed, 291 insertions(+), 11 deletions(-) create mode 100644 src/components/Blocks/Gist/Data.jsx create mode 100644 src/components/Blocks/Gist/DefaultView.jsx create mode 100644 src/components/Blocks/Gist/Edit.jsx create mode 100644 src/components/Blocks/Gist/View.jsx create mode 100644 src/components/Blocks/Gist/schema.js create mode 100644 src/components/Caption/Caption.jsx diff --git a/package.json b/package.json index 3fe80dc..ca95248 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ }, "dependencies": { "prismjs": "1.29.0", - "mermaid": "10.8.0" + "mermaid": "10.8.0", + "react-gist": "1.2.4" }, "devDependencies": { "@plone/scripts": "^3.0.0", diff --git a/src/components/Blocks/Code/DefaultView.jsx b/src/components/Blocks/Code/DefaultView.jsx index 98a82ca..a6ff73b 100644 --- a/src/components/Blocks/Code/DefaultView.jsx +++ b/src/components/Blocks/Code/DefaultView.jsx @@ -1,9 +1,11 @@ import React from 'react'; import SyntaxHighlighter from '../../SyntaxHighlighter/SyntaxHighlighter'; +import Caption from '../../Caption/Caption'; const CodeView = (props) => { const { data } = props; const { code, style, language, lineNbr, showLineNumbers, wrapLongLines } = data; + const { caption_title, caption_description } = data; const styleWrap = wrapLongLines ? 'wrapLongLines' : ''; const className = `code-block-wrapper ${style} ${styleWrap}`; @@ -16,6 +18,7 @@ const CodeView = (props) => { )} + {caption_title && } ); }; diff --git a/src/components/Blocks/Code/Edit.jsx b/src/components/Blocks/Code/Edit.jsx index d4eb628..5b09ea9 100644 --- a/src/components/Blocks/Code/Edit.jsx +++ b/src/components/Blocks/Code/Edit.jsx @@ -3,6 +3,7 @@ import { withBlockExtensions } from '@plone/volto/helpers'; import { SidebarPortal } from '@plone/volto/components'; import config from '@plone/volto/registry'; import CodeBlockData from './Data'; +import Caption from '../../Caption/Caption.jsx'; import Editor from '../../Editor/Editor.tsx'; import { highlight } from 'prismjs/components/prism-core'; @@ -19,7 +20,7 @@ const CodeBlockEdit = (props) => { } const allLanguages = config.settings.codeBlock.languages; const language = allLanguages[data.language].language; - + const { caption_title, caption_description } = data; const handleChange = (code) => { setCode(code); onChangeBlock(block, { ...data, code: code }); @@ -29,6 +30,7 @@ const CodeBlockEdit = (props) => {
handleChange(code)} highlight={(code) => highlight(code, language)} padding={10} preClassName={`code-block-wrapper ${data.style} language-${data.language}`} /> + {caption_title && } diff --git a/src/components/Blocks/Code/schema.js b/src/components/Blocks/Code/schema.js index 1c06138..3a95453 100644 --- a/src/components/Blocks/Code/schema.js +++ b/src/components/Blocks/Code/schema.js @@ -27,6 +27,14 @@ const messages = defineMessages({ id: 'Starting Line Number', defaultMessage: 'Starting Line Number', }, + caption_title: { + id: 'Title', + defaultMessage: 'Title', + }, + caption_description: { + id: 'Description', + defaultMessage: 'Description', + }, }); export const codeSchema = (props) => { @@ -53,6 +61,11 @@ export const codeSchema = (props) => { title: 'Default', fields: ['language', 'style', 'showLineNumbers', 'wrapLongLines', 'lineNbr'], }, + { + id: 'caption', + title: 'Caption', + fields: ['caption_title', 'caption_description'], + }, ], properties: { @@ -83,6 +96,13 @@ export const codeSchema = (props) => { type: 'integer', default: 1, }, + caption_title: { + title: props.intl.formatMessage(messages.caption_title), + }, + caption_description: { + title: props.intl.formatMessage(messages.caption_description), + widget: 'textarea', + }, }, required: ['language'], }; diff --git a/src/components/Blocks/Gist/Data.jsx b/src/components/Blocks/Gist/Data.jsx new file mode 100644 index 0000000..9408a37 --- /dev/null +++ b/src/components/Blocks/Gist/Data.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { BlockDataForm } from '@plone/volto/components'; +import { gistSchema } from './schema'; +import { useIntl } from 'react-intl'; + +const GistBlockData = (props) => { + const { data, block, onChangeBlock } = props; + const intl = useIntl(); + const schema = gistSchema({ ...props, intl }); + + return ( + { + onChangeBlock(block, { + ...data, + [id]: value, + }); + }} + formData={data} + block={block} + /> + ); +}; + +export default GistBlockData; diff --git a/src/components/Blocks/Gist/DefaultView.jsx b/src/components/Blocks/Gist/DefaultView.jsx new file mode 100644 index 0000000..226489c --- /dev/null +++ b/src/components/Blocks/Gist/DefaultView.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import Gist from 'react-gist'; +import Caption from '../../Caption/Caption'; + +const GistView = (props) => { + const { file, gistId, caption_title, caption_description } = props; + return ( + <> + {gistId && } + {caption_title && } + + ); +}; + +export default GistView; diff --git a/src/components/Blocks/Gist/Edit.jsx b/src/components/Blocks/Gist/Edit.jsx new file mode 100644 index 0000000..6796727 --- /dev/null +++ b/src/components/Blocks/Gist/Edit.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { withBlockExtensions } from '@plone/volto/helpers'; +import { SidebarPortal } from '@plone/volto/components'; +import GistBlockData from './Data'; +import View from './DefaultView'; + +const GistBlockEdit = (props) => { + const { data, selected, block } = props; + const { gistId, file } = data; + const { caption_title, caption_description } = data; + return ( +
+ {data && ( + <> +
+ + + )} + + + +
+ ); +}; + +export default withBlockExtensions(GistBlockEdit); diff --git a/src/components/Blocks/Gist/View.jsx b/src/components/Blocks/Gist/View.jsx new file mode 100644 index 0000000..4b0baa9 --- /dev/null +++ b/src/components/Blocks/Gist/View.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { withBlockExtensions } from '@plone/volto/helpers'; +import GistView from './DefaultView'; + +const GistBlockView = (props) => { + const { data, block } = props; + const { gistId, file, caption_title, caption_description } = data; + return ( +
+ {data && } +
+ ); +}; + +export default withBlockExtensions(GistBlockView); diff --git a/src/components/Blocks/Gist/schema.js b/src/components/Blocks/Gist/schema.js new file mode 100644 index 0000000..cadc1d0 --- /dev/null +++ b/src/components/Blocks/Gist/schema.js @@ -0,0 +1,59 @@ +import { defineMessages } from 'react-intl'; + +const messages = defineMessages({ + gistBlock: { + id: 'Gist Block', + defaultMessage: 'Gist Block', + }, + gistId: { + id: 'Gist Id', + defaultMessage: 'Gist Id', + }, + file: { + id: 'File', + defaultMessage: 'File', + }, + caption_title: { + id: 'Title', + defaultMessage: 'Title', + }, + caption_description: { + id: 'Description', + defaultMessage: 'Description', + }, +}); + +export const gistSchema = (props) => { + return { + title: props.intl.formatMessage(messages.gistBlock), + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: ['gistId', 'file'], + }, + { + id: 'caption', + title: 'Caption', + fields: ['caption_title', 'caption_description'], + }, + ], + + properties: { + gistId: { + title: props.intl.formatMessage(messages.gistId), + }, + file: { + title: props.intl.formatMessage(messages.file), + }, + caption_title: { + title: props.intl.formatMessage(messages.caption_title), + }, + caption_description: { + title: props.intl.formatMessage(messages.caption_description), + widget: 'textarea', + }, + }, + required: ['gistId'], + }; +}; diff --git a/src/components/Caption/Caption.jsx b/src/components/Caption/Caption.jsx new file mode 100644 index 0000000..6613fb8 --- /dev/null +++ b/src/components/Caption/Caption.jsx @@ -0,0 +1,39 @@ +/** + * @module components/Caption + */ +import React from 'react'; +import PropTypes from 'prop-types'; + +/** + * Image/video caption component class. + * @function Caption + * @params {string} title Caption title. + * @params {string} description Caption description. + * @returns {string} Markup of the component. + */ +const Caption = ({ title, description }) => { + return ( +
+ {title &&

{title}

} + {description && ( +

+ {description.split('\n').map((line, index) => ( +

{line || '\u00A0'}

+ ))} +

+ )} +
+ ); +}; + +/** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ +Caption.propTypes = { + title: PropTypes.string, + description: PropTypes.string, +}; + +export default Caption; diff --git a/src/index.js b/src/index.js index e96773b..40206b5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,18 @@ import codeSVG from '@plone/volto/icons/code.svg'; import showcaseSVG from '@plone/volto/icons/showcase.svg'; -import CodeBlockViewBlock from './components/Blocks/Code/View'; -import CodeBlockEditBlock from './components/Blocks/Code/Edit'; +// Blocks - CodeBlock +import CodeBlockView from './components/Blocks/Code/View'; +import CodeBlockEdit from './components/Blocks/Code/Edit'; +// Blocks - MermaidBlock import MermaidBlockEdit from './components/Blocks/Mermaid/Edit'; import MermaidBlockView from './components/Blocks/Mermaid/View'; +// Blocks - GistBlock +import GistBlockEdit from './components/Blocks/Gist/Edit'; +import GistBlockView from './components/Blocks/Gist/View'; + import './theme/main.less'; import './theme/theme-dark.less'; import './theme/theme-light.less'; @@ -34,8 +40,8 @@ const applyConfig = (config) => { title: 'Code Block', icon: codeSVG, group: 'text', - view: CodeBlockViewBlock, - edit: CodeBlockEditBlock, + view: CodeBlockView, + edit: CodeBlockEdit, restricted: false, mostUsed: false, sidebarTab: 1, @@ -57,6 +63,19 @@ const applyConfig = (config) => { blockHasOwnFocusManagement: true, }; + config.blocks.blocksConfig.gistBlock = { + id: 'gistBlock', + title: 'Gist Block', + icon: codeSVG, + group: 'text', + view: GistBlockView, + edit: GistBlockEdit, + restricted: false, + mostUsed: false, + sidebarTab: 1, + blockHasOwnFocusManagement: false, + }; + config.settings['codeBlock'] = { languages: { plain: { label: 'Plaintext', language: languages.plain }, @@ -76,11 +95,19 @@ const applyConfig = (config) => { }, }; - // Check for @kitconcept/volto-blocks-grid - const gridBlock = config.blocks.blocksConfig.__grid; - if (gridBlock !== undefined) { - config.blocks.blocksConfig.__grid.gridAllowedBlocks = [...gridBlock.gridAllowedBlocks, 'codeBlock', 'mermaidBlock']; - } + // Add Blocks to gridBlock and accordionBlock + ['gridBlock', 'accordion'].forEach((blockId) => { + const block = config.blocks.blocksConfig[blockId]; + if (block !== undefined) { + config.blocks.blocksConfig.gridBlock = { + ...config.blocks.blocksConfig.gridBlock, + blocksConfig: { + ...config.blocks.blocksConfig, + }, + allowedBlocks: [...config.blocks.blocksConfig.gridBlock.allowedBlocks, 'codeBlock', 'mermaidBlock', 'gistBlock'], + }; + } + }); return config; }; diff --git a/src/theme/main.less b/src/theme/main.less index e278d76..e099892 100644 --- a/src/theme/main.less +++ b/src/theme/main.less @@ -47,3 +47,49 @@ } } } + +.block.gist { + &.edit { + position: relative; + + iframe { + width: 100%; + height: 100%; + } + + .gist.editLayer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.1); + } + } +} + +div.codeBlockCaption { + margin: 0 5px; + color: #000; + font-size: 14px; + font-weight: 300; + line-height: 18px; + text-align: left; + white-space: initial; + + .title { + margin-bottom: 10px; + font-size: 14px; + font-weight: 700; + line-height: 16px; + } + + .description { + p { + margin-bottom: 10px; + font-size: 14px; + font-weight: 300; + line-height: 16px; + } + } +}