diff --git a/CHANGELOG.md b/CHANGELOG.md index bd72e35e..d927dceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,17 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). -### [1.32.0](https://github.com/eea/volto-eea-website-theme/compare/1.31.0...1.32.0) - 21 March 2024 +### [1.32.1](https://github.com/eea/volto-eea-website-theme/compare/1.32.0...1.32.1) - 26 March 2024 #### :rocket: New Features - feat(slate): add new h3 slate element [Miu Razvan - [`20a7e93`](https://github.com/eea/volto-eea-website-theme/commit/20a7e9316d5c1de279712c38bc05eb775f8bd326)] - feat(slate): add h4 in slate toolbar [Miu Razvan - [`2c523ac`](https://github.com/eea/volto-eea-website-theme/commit/2c523acdeb78ed224c4a37285c48dbf4555604e0)] +#### :bug: Bug Fixes + +- fix(slate): insert/remove element fix, refs #261770 [Miu Razvan - [`b121ffd`](https://github.com/eea/volto-eea-website-theme/commit/b121ffd2de5c5973f9819b949be1c83400d72edb)] + #### :nail_care: Enhancements - change(slate): Use F as icon and renamed title of new heading Slate toolbar button to Figure title [David Ichim - [`7355bb3`](https://github.com/eea/volto-eea-website-theme/commit/7355bb3f3cf527c24fc41a10f72cc854773ab84b)] @@ -23,8 +27,12 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). #### :hammer_and_wrench: Others +- fix conflicts [Miu Razvan - [`9ad80fc`](https://github.com/eea/volto-eea-website-theme/commit/9ad80fc98a3057d96f573150456bc43b5f822782)] +- fix conflicts [Miu Razvan - [`dee94db`](https://github.com/eea/volto-eea-website-theme/commit/dee94db33c47a2d963ea912712723768de547719)] - Update package.json [ichim-david - [`9db96ff`](https://github.com/eea/volto-eea-website-theme/commit/9db96ff3f8e8eedce92c53befe6a1d6e965d8699)] - Revert "(fix): In search block on edit, the sort on and sort order are not working (#205)" [David Ichim - [`2045d50`](https://github.com/eea/volto-eea-website-theme/commit/2045d50b3d2f18fab0d6c6794d8030975b1a3f21)] +### [1.32.0](https://github.com/eea/volto-eea-website-theme/compare/1.31.0...1.32.0) - 25 March 2024 + ### [1.31.0](https://github.com/eea/volto-eea-website-theme/compare/1.30.0...1.31.0) - 14 March 2024 #### :rocket: New Features diff --git a/README.md b/README.md index f92f1ab8..19814126 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ See [Storybook](https://eea.github.io/eea-storybook/). ## Volto customizations +- `volto-slate/elementEditor/utils` -> https://github.com/plone/volto/pull/5926 + - `volto-slate/editor/SlateEditor` -> When two slates looks at the same prop changing one slate and updating the other should be handled properly. This change makes replacing the old value of slate work in sync with the other slates that watches the same prop [ref](https://taskman.eionet.europa.eu/issues/264239#note-11). **!!IMPORTANT**: This change requires volto@^16.26.1 diff --git a/package.json b/package.json index 553a8d61..78e17702 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@eeacms/volto-eea-website-theme", - "version": "1.32.0", + "version": "1.32.1", "description": "@eeacms/volto-eea-website-theme: Volto add-on", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team", @@ -79,4 +79,4 @@ "cypress:open": "make cypress-open", "prepare": "husky install" } -} +} \ No newline at end of file diff --git a/src/customizations/@plone/volto-slate/elementEditor/utils.js b/src/customizations/@plone/volto-slate/elementEditor/utils.js new file mode 100644 index 00000000..a219c359 --- /dev/null +++ b/src/customizations/@plone/volto-slate/elementEditor/utils.js @@ -0,0 +1,245 @@ +import { Editor, Transforms, Node } from 'slate'; + +/** + * @description Creates or updates an existing $elementType. It also takes care + * of the saved selection and uses PathRef. + * + * @param {Editor} editor The Slate editor for the context + * @param {object} data Relevant data for this element + * + * @returns {boolean} true if an element was possibly inserted, false otherwise + * (currently we do not check here if the element was already applied to the + * editor) + */ +export const _insertElement = (elementType) => (editor, data) => { + if (editor.getSavedSelection()) { + const selection = editor.selection || editor.getSavedSelection(); + + const rangeRef = Editor.rangeRef(editor, selection); + + const res = Array.from( + Editor.nodes(editor, { + match: (n) => n.type === elementType, + mode: 'highest', + at: selection, + }), + ); + + if (res.length) { + const [, path] = res[0]; + Transforms.setNodes( + editor, + { data }, + { + at: path ? path : null, + match: path ? (n) => n.type === elementType : null, + }, + ); + } else { + Transforms.wrapNodes( + editor, + { type: elementType, data }, + { + split: true, + at: selection, + match: (node) => { + return Node.string(node).length !== 0; + }, + }, + ); + } + + const sel = JSON.parse(JSON.stringify(rangeRef.current)); + + setTimeout(() => { + Transforms.select(editor, sel); + editor.setSavedSelection(sel); + }); + + return true; + } + + return false; +}; + +/** + * Will unwrap a node that has as type the one received or one from an array. + * It identifies the current target element and expands the selection to it, in + * case the selection was just partial. This allows a "clear and reassign" + * operation, for example for the Link plugin. + * + * @param {string|Object[]} elementType - this can be a string or an array of strings + * @returns {Object|null} - current node + */ +export const _unwrapElement = (elementType) => (editor) => { + const selection = editor.selection || editor.getSavedSelection(); + let [link] = Editor.nodes(editor, { + at: selection, + match: (node) => node?.type === elementType, + }); + const isAtStart = + selection.anchor.offset === 0 && selection.focus.offset === 0; + + if (!link && !isAtStart) return false; + + if (!link) { + try { + link = Editor.previous(editor, { + at: selection.anchor.path, + }); + } catch (ex) { + link = []; + } + } + + const [, path] = link; + const [start, end] = Editor.edges(editor, path); + const range = { anchor: start, focus: end }; + + const ref = Editor.rangeRef(editor, range); + + Transforms.select(editor, range); + Transforms.unwrapNodes(editor, { + match: (n) => + Array.isArray(elementType) + ? elementType.includes(n.type) + : n.type === elementType, + at: range, + }); + + const current = ref.current; + ref.unref(); + + return current; +}; + +export const _isActiveElement = (elementType) => (editor) => { + const selection = editor.selection || editor.getSavedSelection(); + let found; + try { + found = Array.from( + Editor.nodes(editor, { + match: (n) => n.type === elementType, + at: selection, + }) || [], + ); + } catch (e) { + // eslint-disable-next-line + // console.warn('Error in finding active element', e); + return false; + } + if (found.length) 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 === elementType) { + return true; + } + } + } + + return false; +}; + +/** + * Will look for a node that has as type the one received or one from an array + * @param {string|Object[]} elementType - this can be a string or an array of strings + * @returns {Object|null} - found node + */ +export const _getActiveElement = (elementType) => ( + editor, + direction = 'any', +) => { + const selection = editor.selection || editor.getSavedSelection(); + let found = []; + + try { + found = Array.from( + Editor.nodes(editor, { + match: (n) => + Array.isArray(elementType) + ? elementType.includes(n.type) + : n.type === elementType, + at: selection, + }), + ); + } catch (e) { + return null; + } + + if (found.length) return found[0]; + + if (!selection) return null; + + if (direction === 'any' || direction === 'backward') { + const { path } = selection.anchor; + const isAtStart = + selection.anchor.offset === 0 && selection.focus.offset === 0; + + if (isAtStart) { + let found; + try { + found = Editor.previous(editor, { + at: path, + }); + } catch (ex) { + // eslint-disable-next-line no-console + console.warn('Unable to find previous node', editor, path); + return; + } + if (found && found[0] && found[0].type === elementType) { + if ( + (Array.isArray(elementType) && elementType.includes(found[0].type)) || + found[0].type === elementType + ) { + return found; + } + } else { + return null; + } + } + } + + if (direction === 'any' || direction === 'forward') { + const { path } = selection.anchor; + const isAtStart = + selection.anchor.offset === 0 && selection.focus.offset === 0; + + if (isAtStart) { + let found; + try { + found = Editor.next(editor, { + at: path, + }); + } catch (e) { + // eslint-disable-next-line + console.warn('Unable to find next node', editor, path); + return; + } + if (found && found[0] && found[0].type === elementType) { + if ( + (Array.isArray(elementType) && elementType.includes(found[0].type)) || + found[0].type === elementType + ) { + return found; + } + } else { + return null; + } + } + } + + return null; +};