diff --git a/acceptance/cypress/tests/blocks.cy.js b/acceptance/cypress/tests/block_code.cy.js similarity index 93% rename from acceptance/cypress/tests/blocks.cy.js rename to acceptance/cypress/tests/block_code.cy.js index b84117d..23ecbc8 100644 --- a/acceptance/cypress/tests/blocks.cy.js +++ b/acceptance/cypress/tests/block_code.cy.js @@ -1,5 +1,5 @@ -context('Blocks Acceptance Tests', () => { - describe('Text Block Tests', () => { +context('Code Block Acceptance Tests', () => { + describe('Code Block Tests', () => { beforeEach(() => { // given a logged in editor and a page in edit mode cy.visit('/'); diff --git a/acceptance/cypress/tests/block_mermaid.cy.js b/acceptance/cypress/tests/block_mermaid.cy.js new file mode 100644 index 0000000..1058c29 --- /dev/null +++ b/acceptance/cypress/tests/block_mermaid.cy.js @@ -0,0 +1,39 @@ +context('Mermaid Block Acceptance Tests', () => { + describe('Mermaid Block Tests', () => { + beforeEach(() => { + // given a logged in editor and a page in edit mode + cy.visit('/'); + cy.autologin(); + cy.createContent({ + contentType: 'Document', + contentId: 'document', + contentTitle: 'Test document', + }); + cy.createContent({ + contentType: 'Document', + contentId: 'my-page', + contentTitle: 'My Page', + path: '/document', + }); + cy.visit('/document'); + cy.navigate('/document/edit'); + }); + + it('As editor I can add a Mermaid Block', function () { + cy.intercept('PATCH', '/**/document').as('edit'); + cy.intercept('GET', '/**/document').as('content'); + cy.intercept('GET', '/**/Document').as('schema'); + + cy.getSlate().click(); + cy.get('.button .block-add-button').click({ force: true }); + cy.get('.blocks-chooser .text .button.mermaidBlock').click({ force: true }); + cy.get('pre.language-mermaid + textarea') + .click() + .type('sequenceDiagram\n Alice->>John: Hello John, how are you?\nJohn-->>Alice: Great!\nAlice-)John: See you later!'); + cy.get('#toolbar-save').click(); + cy.wait(100); + cy.get('.mermaidWrapper > div > svg') + .should('be.visible') + }); + }); +}); diff --git a/dockerfiles/docker-compose.yml b/dockerfiles/docker-compose.yml index 7aab214..2cb9439 100644 --- a/dockerfiles/docker-compose.yml +++ b/dockerfiles/docker-compose.yml @@ -9,12 +9,18 @@ services: args: ADDON_NAME: "${ADDON_NAME}" ADDON_PATH: "${ADDON_PATH}" - VOLTO_VERSION: ${VOLTO_VERSION:-17} + VOLTO_VERSION: "${VOLTO_VERSION:-17}" volumes: - ${CURRENT_DIR}:/app/src/addons/${ADDON_PATH}/ environment: RAZZLE_INTERNAL_API_PATH: http://backend:8080/Plone - RAZZLE_API_PATH: http://localhost:8080/Plone + # In case that you want to connect to and outside (non-docker) local instance + # coment the above, use the next line + # RAZZLE_INTERNAL_API_PATH: http://host.docker.internal:8080/Plone + RAZZLE_API_PATH: http://127.0.0.1:8080/Plone + HOST: 0.0.0.0 + depends_on: + - backend ports: - 3000:3000 - 3001:3001 diff --git a/dockerfiles/storybook.js b/dockerfiles/storybook.js index 5cef161..37126a7 100644 --- a/dockerfiles/storybook.js +++ b/dockerfiles/storybook.js @@ -51,13 +51,7 @@ const defaultRazzleOptions = { staticCssInDev: false, emitOnErrors: false, disableWebpackbar: false, - browserslist: [ - '>1%', - 'last 4 versions', - 'Firefox ESR', - 'not ie 11', - 'not dead', - ], + browserslist: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie 11', 'not dead'], }; module.exports = { @@ -116,9 +110,7 @@ module.exports = { // Put the SVG loader on top and prevent the asset/resource rule // from processing the app's SVGs config.module.rules.unshift(SVGLOADER); - const fileLoaderRule = config.module.rules.find((rule) => - rule.test.test('.svg'), - ); + const fileLoaderRule = config.module.rules.find((rule) => rule.test.test('.svg')); fileLoaderRule.exclude = /icons\/.*\.svg$/; config.plugins.unshift( @@ -139,9 +131,7 @@ module.exports = { }; // Addons have to be loaded with babel - const addonPaths = registry.addonNames.map((addon) => - fs.realpathSync(registry.packages[addon].modulePath), - ); + const addonPaths = registry.addonNames.map((addon) => fs.realpathSync(registry.packages[addon].modulePath)); resultConfig.module.rules[1].exclude = (input) => // exclude every input from node_modules except from @plone/volto /node_modules\/(?!(@plone\/volto)\/)/.test(input) && @@ -150,11 +140,7 @@ module.exports = { const addonExtenders = registry.getAddonExtenders().map((m) => require(m)); - const extendedConfig = addonExtenders.reduce( - (acc, extender) => - extender.modify(acc, { target: 'web', dev: 'dev' }, config), - resultConfig, - ); + const extendedConfig = addonExtenders.reduce((acc, extender) => extender.modify(acc, { target: 'web', dev: 'dev' }, config), resultConfig); // Note: we don't actually support razzle plugins, which are also a feature // of the razzle.extend.js addons file. Those features are probably diff --git a/package.json b/package.json index 0bd307d..3fe80dc 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "@plone/volto": ">=17.0.0" }, "dependencies": { - "prismjs": "1.29.0" + "prismjs": "1.29.0", + "mermaid": "10.8.0" }, "devDependencies": { "@plone/scripts": "^3.0.0", diff --git a/src/components/Blocks/Mermaid/DefaultView.jsx b/src/components/Blocks/Mermaid/DefaultView.jsx index 59f793b..621880f 100644 --- a/src/components/Blocks/Mermaid/DefaultView.jsx +++ b/src/components/Blocks/Mermaid/DefaultView.jsx @@ -1,19 +1,36 @@ import React, { useEffect, useState } from 'react'; const MermaidView = (props) => { - const { code } = props; - const [loaded, setLoaded] = useState(false); + const { code, block } = props; + const elementId = `mermaid-${block}`; + const [mermaid, setMermaid] = useState(null); + const [svg, setSVG] = useState(''); + useEffect(() => { - if (window.mermaid) { - setLoaded(true); - } - }, [loaded]); + const loadMermaid = async () => { + const config = { startOnLoad: true, flowchart: { useMaxWidth: false, htmlLabels: true } }; + const { default: mermaid } = await import('mermaid/dist/mermaid.esm.mjs'); + setMermaid(mermaid); + mermaid.initialize(config); + }; + + loadMermaid(); + return () => {}; + }, []); + useEffect(() => { - if (loaded && window.mermaid) { - window.mermaid.contentLoaded(); + if (__CLIENT__ && mermaid && elementId && code) { + const renderSVG = async () => { + const { svg } = await mermaid.render(elementId, code); + setSVG(svg); + }; + + renderSVG(); + return () => {}; } - }, [loaded, code]); - return <>{code &&
{code}
}; + }, [mermaid, elementId, code]); + + return
{svg &&
}
; }; export default MermaidView; diff --git a/src/components/Blocks/Mermaid/View.jsx b/src/components/Blocks/Mermaid/View.jsx index f98e4cf..87b7ba4 100644 --- a/src/components/Blocks/Mermaid/View.jsx +++ b/src/components/Blocks/Mermaid/View.jsx @@ -3,9 +3,9 @@ import MermaidView from './DefaultView'; import { withBlockExtensions } from '@plone/volto/helpers'; const MermaidBlockView = (props) => { - const { data } = props; + const { block, data } = props; const code = data.code || ''; - return <>{code && }; + return <>{code && }; }; export default withBlockExtensions(MermaidBlockView); diff --git a/src/helpers/Mermaid/MermaidConfig.jsx b/src/helpers/Mermaid/MermaidConfig.jsx deleted file mode 100644 index fdda91b..0000000 --- a/src/helpers/Mermaid/MermaidConfig.jsx +++ /dev/null @@ -1,19 +0,0 @@ -const MermaidConfig = () => { - const script = ` - import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; - window.mermaid = mermaid; - mermaid.initialize( - { startOnLoad: true } - ); - `; - return ( -