diff --git a/cypress/tests/core/blocks/block-anchors.js b/cypress/tests/core/blocks/block-anchors.js new file mode 100644 index 0000000000..1166b0ce26 --- /dev/null +++ b/cypress/tests/core/blocks/block-anchors.js @@ -0,0 +1,71 @@ +import { slateBeforeEach } from '../../../support/volto-slate'; + +describe('Block Tests: Anchors', () => { + beforeEach(slateBeforeEach); + + it('Add Block: Links', () => { + // Change page title + cy.clearSlateTitle(); + cy.getSlateTitle().type('Slate Heading Anchors'); + cy.getSlate().click(); + + // Add TOC block + cy.get('.ui.basic.icon.button.block-add-button').first().click(); + cy.get(".blocks-chooser .ui.form .field.searchbox input[type='text']").type( + 'table of contents', + ); + cy.get('.button.toc').click(); + + // Save page + cy.get('#toolbar-save').click(); + cy.url().should('eq', Cypress.config().baseUrl + '/my-page'); + cy.get('h1.documentFirstHeading') + .trigger('mouseover', { eventConstructor: 'MouseEvent' }) + .children() + .should('have.length', 1); + }); + + it('Add Block: add content to TOC', () => { + // Change page title + cy.clearSlateTitle(); + cy.getSlateTitle().type('Slate Heading Anchors'); + cy.getSlate().click(); + + // Add TOC block + cy.get('.ui.basic.icon.button.block-add-button').first().click(); + cy.get(".blocks-chooser .ui.form .field.searchbox input[type='text']").type( + 'table of contents', + ); + cy.get('.button.toc').click(); + + // Add headings + cy.get('.ui.drag.block.inner.slate').click().type('Title 1').click(); + cy.get('.ui.drag.block.inner.slate span span span').setSelection('Title 1'); + cy.get('.slate-inline-toolbar .button-wrapper a[title="Title"]').click({ + force: true, + }); + cy.get('.ui.drag.block.inner.slate').click().type('{enter}'); + + cy.get('.ui.drag.block.inner.slate').eq(1).click().type('Title 2').click(); + cy.get('.ui.drag.block.inner.slate span span span') + .eq(1) + .setSelection('Title 2'); + cy.get('.slate-inline-toolbar .button-wrapper a[title="Title"]').click({ + force: true, + }); + cy.get('.ui.drag.block.inner.slate').eq(1).click().type('{enter}'); + + // Save page + cy.get('#toolbar-save').click(); + cy.url().should('eq', Cypress.config().baseUrl + '/my-page'); + + // Check if the page contains the TOC and scrolls to each entry on click + cy.contains('Slate Heading Anchors'); + cy.get('h2[id="title-1"]').contains('Title 1'); + cy.get('h2[id="title-2"]').contains('Title 2'); + cy.get('a[href="#title-1"]').click(); + cy.get('h2[id="title-1"]').scrollIntoView().should('be.visible'); + cy.get('a[href="#title-2"]').click(); + cy.get('h2[id="title-2"]').scrollIntoView().should('be.visible'); + }); +}); diff --git a/cypress/tests/core/guillotina/blocks-text.js b/cypress/tests/core/guillotina/blocks-text.js index 8ca7b815b8..334c3b3c32 100644 --- a/cypress/tests/core/guillotina/blocks-text.js +++ b/cypress/tests/core/guillotina/blocks-text.js @@ -53,7 +53,7 @@ describe('Text Block Tests', () => { // then the page view should contain a link cy.contains('Colorless green ideas sleep furiously.'); - cy.get('#page-document a') + cy.get('#page-document p a') .should('have.attr', 'href') .and('include', 'https://google.com'); }); @@ -81,7 +81,7 @@ describe('Text Block Tests', () => { // then the page view should contain a mailto link cy.contains('Colorless green ideas sleep furiously.'); - cy.get('#page-document a') + cy.get('#page-document p a') .should('have.attr', 'href') .and('include', 'mailto:hello@example.com'); }); diff --git a/cypress/tests/core/volto-slate/05-block-slate-format-basics.js b/cypress/tests/core/volto-slate/05-block-slate-format-basics.js index 413d43a21b..3d32b822e4 100644 --- a/cypress/tests/core/volto-slate/05-block-slate-format-basics.js +++ b/cypress/tests/core/volto-slate/05-block-slate-format-basics.js @@ -135,7 +135,7 @@ describe('Block Tests: Basic text format', () => { cy.toolbarSave(); // then the page view should contain our changes - cy.get('[id="page-document"] h2').children().should('have.length', 0); + cy.get('[id="page-document"] h2').children().should('have.length', 1); cy.get('[id="page-document"] h2').contains('Colorless'); }); @@ -177,7 +177,7 @@ describe('Block Tests: Basic text format', () => { cy.toolbarSave(); // then the page view should contain our changes - cy.get('[id="page-document"] h3').children().should('have.length', 0); + cy.get('[id="page-document"] h3').children().should('have.length', 1); cy.get('[id="page-document"] h3').contains('Colorless'); }); diff --git a/cypress/tests/guillotina/blocks-text.js b/cypress/tests/guillotina/blocks-text.js index 8ca7b815b8..334c3b3c32 100644 --- a/cypress/tests/guillotina/blocks-text.js +++ b/cypress/tests/guillotina/blocks-text.js @@ -53,7 +53,7 @@ describe('Text Block Tests', () => { // then the page view should contain a link cy.contains('Colorless green ideas sleep furiously.'); - cy.get('#page-document a') + cy.get('#page-document p a') .should('have.attr', 'href') .and('include', 'https://google.com'); }); @@ -81,7 +81,7 @@ describe('Text Block Tests', () => { // then the page view should contain a mailto link cy.contains('Colorless green ideas sleep furiously.'); - cy.get('#page-document a') + cy.get('#page-document p a') .should('have.attr', 'href') .and('include', 'mailto:hello@example.com'); }); diff --git a/docs/source/configuration/volto-slate/configuration-settings.md b/docs/source/configuration/volto-slate/configuration-settings.md index a6da06c3b2..97276b804f 100644 --- a/docs/source/configuration/volto-slate/configuration-settings.md +++ b/docs/source/configuration/volto-slate/configuration-settings.md @@ -239,6 +239,19 @@ They are not persisted in the final value, so they are useful, for example, to h slate.runtimeDecorators = [([node, path], ranges) => ranges]; ``` +(editor-configuration-slate-useLinkedHeadings-label)= + +## `slate.useLinkedHeadings` + +The setting `slate.useLinkedHeadings` controls whether `volto-slate` creates anchors for headings, such as `h1` and `h2`, in the editor. + +The default setting is `true`. + +You can opt out of this feature by setting its value to `false`. + +```js +slate.useLinkedHeadings = false +``` (editor-configuration-blocks-initialBlocksFocus-label)= diff --git a/locales/ca/LC_MESSAGES/volto.po b/locales/ca/LC_MESSAGES/volto.po index 4bf4b148d1..db73f21323 100644 --- a/locales/ca/LC_MESSAGES/volto.po +++ b/locales/ca/LC_MESSAGES/volto.po @@ -1903,6 +1903,11 @@ msgstr "" msgid "Link" msgstr "Enllaç" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index f316a86a30..4d37f47225 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -1900,6 +1900,11 @@ msgstr "" msgid "Link" msgstr "Link" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index 08c9c94371..7783ecbea7 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -1894,6 +1894,11 @@ msgstr "" msgid "Link" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index 6d18f20d8c..0f8a0bd273 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -1905,6 +1905,11 @@ msgstr "" msgid "Link" msgstr "Enlace" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/eu/LC_MESSAGES/volto.po b/locales/eu/LC_MESSAGES/volto.po index 6f2565e6d5..85aa21e868 100644 --- a/locales/eu/LC_MESSAGES/volto.po +++ b/locales/eu/LC_MESSAGES/volto.po @@ -1901,6 +1901,11 @@ msgstr "" msgid "Link" msgstr "Esteka" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/fi/LC_MESSAGES/volto.po b/locales/fi/LC_MESSAGES/volto.po index 791c210c1e..e3d3b2cc3f 100644 --- a/locales/fi/LC_MESSAGES/volto.po +++ b/locales/fi/LC_MESSAGES/volto.po @@ -1905,6 +1905,11 @@ msgstr "" msgid "Link" msgstr "Linkki" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/fr/LC_MESSAGES/volto.po b/locales/fr/LC_MESSAGES/volto.po index c4a007b100..8862a6e1ac 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -1911,6 +1911,11 @@ msgstr "" msgid "Link" msgstr "Lien" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index d63d8a20f0..c081ae1612 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -1894,6 +1894,11 @@ msgstr "" msgid "Link" msgstr "Link" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/ja/LC_MESSAGES/volto.po b/locales/ja/LC_MESSAGES/volto.po index a8a6fbea17..81b4228ce9 100644 --- a/locales/ja/LC_MESSAGES/volto.po +++ b/locales/ja/LC_MESSAGES/volto.po @@ -1902,6 +1902,11 @@ msgstr "" msgid "Link" msgstr "リンク" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/nl/LC_MESSAGES/volto.po b/locales/nl/LC_MESSAGES/volto.po index e575532f1a..24d58061a5 100644 --- a/locales/nl/LC_MESSAGES/volto.po +++ b/locales/nl/LC_MESSAGES/volto.po @@ -1913,6 +1913,11 @@ msgstr "" msgid "Link" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/pt/LC_MESSAGES/volto.po b/locales/pt/LC_MESSAGES/volto.po index 2f445ad4fd..b5ecc4dd7d 100644 --- a/locales/pt/LC_MESSAGES/volto.po +++ b/locales/pt/LC_MESSAGES/volto.po @@ -1902,6 +1902,11 @@ msgstr "" msgid "Link" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/pt_BR/LC_MESSAGES/volto.po b/locales/pt_BR/LC_MESSAGES/volto.po index bd0ee20da5..19cfd33d8d 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -1904,6 +1904,11 @@ msgstr "Menos filtros" msgid "Link" msgstr "Link" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/ro/LC_MESSAGES/volto.po b/locales/ro/LC_MESSAGES/volto.po index f2411286fc..e3e7c2ae0c 100644 --- a/locales/ro/LC_MESSAGES/volto.po +++ b/locales/ro/LC_MESSAGES/volto.po @@ -1894,6 +1894,11 @@ msgstr "" msgid "Link" msgstr "Legătură" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/volto.pot b/locales/volto.pot index 7cb0cc5731..4ef602b388 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -1896,6 +1896,11 @@ msgstr "" msgid "Link" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/locales/zh_CN/LC_MESSAGES/volto.po b/locales/zh_CN/LC_MESSAGES/volto.po index 98779199d8..0355a55f65 100644 --- a/locales/zh_CN/LC_MESSAGES/volto.po +++ b/locales/zh_CN/LC_MESSAGES/volto.po @@ -1900,6 +1900,11 @@ msgstr "" msgid "Link" msgstr "链接" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Link copied to clipboard +msgid "Link copied to clipboard" +msgstr "" + #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema # defaultMessage: Link more diff --git a/news/4287.feature b/news/4287.feature new file mode 100644 index 0000000000..e676eb12b7 --- /dev/null +++ b/news/4287.feature @@ -0,0 +1 @@ +Added slug-based linked headings in `volto-slate`. @tiberiuichim, @nileshgulia1 diff --git a/package.json b/package.json index d09b21cb0c..8882758ccf 100644 --- a/package.json +++ b/package.json @@ -289,6 +289,7 @@ "eslint-plugin-react-hooks": "4.0.2", "express": "4.17.3", "filesize": "6", + "github-slugger": "1.4.0", "glob": "7.1.6", "history": "4.10.1", "hoist-non-react-statics": "3.3.2", diff --git a/packages/volto-slate/src/blocks/Text/TextBlockView.jsx b/packages/volto-slate/src/blocks/Text/TextBlockView.jsx index 5e8accb82c..7f9f623fbd 100644 --- a/packages/volto-slate/src/blocks/Text/TextBlockView.jsx +++ b/packages/volto-slate/src/blocks/Text/TextBlockView.jsx @@ -1,26 +1,30 @@ -import { serializeNodes } from '@plone/volto-slate/editor/render'; +import { + serializeNodes, + serializeNodesToText, +} from '@plone/volto-slate/editor/render'; import config from '@plone/volto/registry'; +import { isEqual } from 'lodash'; +import Slugger from 'github-slugger'; const TextBlockView = (props) => { const { id, data, styling = {} } = props; const { value, override_toc } = data; const metadata = props.metadata || props.properties; - return serializeNodes( - value, - (node, path) => { - const res = { ...styling }; - if (node.type) { - if ( - config.settings.slate.topLevelTargetElements.includes(node.type) || - override_toc - ) { - res.id = id; - } + const { topLevelTargetElements } = config.settings.slate; + + const getAttributes = (node, path) => { + const res = { ...styling }; + if (node.type && isEqual(path, [0])) { + if (topLevelTargetElements.includes(node.type) || override_toc) { + const text = serializeNodesToText(node?.children || []); + const slug = Slugger.slug(text); + res.id = slug || id; } - return res; - }, - { metadata: metadata }, - ); + } + return res; + }; + + return serializeNodes(value, getAttributes, { metadata: metadata }); }; export default TextBlockView; diff --git a/packages/volto-slate/src/editor/config.jsx b/packages/volto-slate/src/editor/config.jsx index d97bb021d7..cdacc04daa 100644 --- a/packages/volto-slate/src/editor/config.jsx +++ b/packages/volto-slate/src/editor/config.jsx @@ -43,6 +43,7 @@ import { bTagDeserializer, codeTagDeserializer, } from './deserialize'; +import { renderLinkElement } from './render'; // Registry of available buttons export const buttons = { @@ -234,10 +235,10 @@ export const defaultBlockType = 'p'; export const elements = { default: ({ attributes, children }) =>
{children}
, - h1: ({ attributes, children }) =>