Skip to content

Commit

Permalink
Popup with options to remove metadata within the block (#331)
Browse files Browse the repository at this point in the history
* added functionality to remove blocks along with their saved metadata in the modal component

* build

* improved some ui elements and styles

* build

* removed REST API functions for deleting post metadata

* Fixed multiple issues: implemented hooks and useEntityProp state functions to manage post metadata

* build

* refactored handleConfirmDelete function

* update only selected meta and not all

* simplified component render condition

* added useAllBlocks hook

* fixed error affecting nested blocks and initial contained blocks in editor

* build

* simplified code to detect removed block

changed useAllBlocks hook to always return blocks array

* optimized useAllBlocks hook

* improved remove meta modal code structure and added memoization

* prevent displaying checkboxes for meta, which does not stored in the post

simplified some code parts

* build

---------

Co-authored-by: Nikita <[email protected]>
  • Loading branch information
Fellan-91 and nk-o authored Nov 29, 2024
1 parent af9abb4 commit ce1fde8
Show file tree
Hide file tree
Showing 14 changed files with 251 additions and 47 deletions.
42 changes: 4 additions & 38 deletions assets/editor/extensions/block-id.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,17 @@ import shorthash from 'shorthash';
*/
import { addFilter } from '@wordpress/hooks';
import { useEffect, useRef, useCallback } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { getBlockType } from '@wordpress/blocks';
import { createHigherOrderComponent, useThrottle } from '@wordpress/compose';

import useAllBlocks from '../../hooks/use-all-blocks';

function useBlockID(props) {
const { setAttributes, attributes, clientId, name } = props;
const blockSettings = getBlockType(name);
const didMountRef = useRef(false);

const { getBlocks } = useSelect((select) => {
return select('core/block-editor');
});

/**
* Get recursive all blocks of the current page
*
* @param {boolean} blocks - block list
*
* @return {Array} block list
*/
const getAllBlocks = useCallback(
(blocks = false) => {
let result = [];

if (!blocks) {
blocks = getBlocks();
}

if (!blocks) {
return result;
}

blocks.forEach((data) => {
result.push(data);

if (data.innerBlocks && data.innerBlocks.length) {
result = [...result, ...getAllBlocks(data.innerBlocks)];
}
});

return result;
},
[getBlocks]
);
const allBlocks = useAllBlocks();

const onUpdate = useCallback(
(checkDuplicates) => {
Expand All @@ -62,7 +29,6 @@ function useBlockID(props) {

// prevent unique ID duplication after block duplicated.
if (checkDuplicates) {
const allBlocks = getAllBlocks();
allBlocks.forEach((data) => {
if (
data.clientId &&
Expand Down Expand Up @@ -112,7 +78,7 @@ function useBlockID(props) {
}
}
},
[attributes, clientId, getAllBlocks, name, setAttributes]
[attributes, clientId, allBlocks, name, setAttributes]
);

const onUpdateThrottle = useThrottle(onUpdate, 60);
Expand Down
1 change: 1 addition & 0 deletions assets/editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ import './store';
import './blocks/free';
import './blocks/main';
import './extensions/block-id';
import './plugins';
6 changes: 6 additions & 0 deletions assets/editor/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { registerPlugin } from '@wordpress/plugins';
import RemoveBlockWithSavedMeta from './remove-block-with-saved-post-meta';

registerPlugin('lazy-blocks-remove-block-with-saved-meta', {
render: RemoveBlockWithSavedMeta,
});
185 changes: 185 additions & 0 deletions assets/editor/plugins/remove-block-with-saved-post-meta/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import './index.scss';
// eslint-disable-next-line import/no-extraneous-dependencies
import { differenceWith } from 'lodash';
import { useSelect } from '@wordpress/data';
import { useEffect, useState, useMemo, useCallback } from '@wordpress/element';
import { useEntityProp } from '@wordpress/core-data';
import { Button, ToggleControl } from '@wordpress/components';
import { usePrevious } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';

import Modal from '../../../components/modal';
import useAllBlocks from '../../../hooks/use-all-blocks';

let options = window.lazyblocksGutenberg;
if (!options?.blocks?.length) {
options = {
post_type: 'post',
blocks: [],
controls: {},
};
}

function RemoveBlockWithSavedMeta() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [savedMetaNames, setMetaNames] = useState([]);

const allBlocks = useAllBlocks();
const prevAllBlocks = usePrevious(allBlocks);

const postType = useSelect(
(select) => select('core/editor')?.getCurrentPostType?.(),
[]
);

const [meta, setMeta] = useEntityProp('postType', postType, 'meta');

// Memoize blocks with meta controls
const blocksWithMetaControls = useMemo(() => {
return options.blocks.filter((block) =>
Object.values(block.controls).some(
(control) => control.save_in_meta === 'true'
)
);
}, []);

const handleMetaToggle = useCallback((index, value) => {
setMetaNames((prev) =>
prev.map((prevMeta, i) =>
i === index ? { ...prevMeta, checked: value } : prevMeta
)
);
}, []);

const handleConfirmDelete = useCallback(() => {
const metaToDelete = savedMetaNames.reduce((acc, savedMeta) => {
if (savedMeta.checked) {
acc[savedMeta.metaName] = null;
}

return acc;
}, {});

if (Object.keys(metaToDelete).length) {
setMeta(metaToDelete);
}

setIsModalOpen(false);
}, [savedMetaNames, setMeta]);

const handleCancel = useCallback(() => {
setIsModalOpen(false);
}, []);

useEffect(() => {
if (!prevAllBlocks) {
return;
}

const removedBlocks = differenceWith(
prevAllBlocks,
allBlocks,
(el1, el2) => el1.clientId === el2.clientId
);

if (!removedBlocks.length) {
return;
}

const savedSlugs = blocksWithMetaControls.map((block) => block.slug);

const processRemovedBlocks = () => {
const uniqueMetaNames = new Set();

const metaNames = removedBlocks
.filter((block) => savedSlugs.includes(block.name))
.flatMap((block) => {
const savedBlock = blocksWithMetaControls.find(
(saved) => saved.slug === block.name
);

return Object.values(savedBlock.controls)
.filter((control) => control.save_in_meta === 'true')
.map((control) => ({
metaName: control.save_in_meta_name || control.name,
label: control.label,
default: control.default,
checked: false,
}))
.filter((control) => {
if (uniqueMetaNames.has(control.metaName)) {
return false;
}

uniqueMetaNames.add(control.metaName);

return true;
});
})
.filter((m) => {
const metaValue = meta[m.metaName];

return (
metaValue !== m.default &&
metaValue !== undefined &&
metaValue !== null &&
metaValue !== ''
);
});

if (metaNames?.length) {
setMetaNames(metaNames);
setIsModalOpen(true);
}
};

processRemovedBlocks();
}, [meta, allBlocks, prevAllBlocks, blocksWithMetaControls]);

if (!isModalOpen || !savedMetaNames.length) {
return null;
}

return (
<Modal
title={__('Remove post meta used by this block?', 'lazy-blocks')}
onRequestClose={handleCancel}
size="medium"
>
<p style={{ marginTop: 0 }}>
{__(
'This block created metadata that is still saved in your post.',
'lazy-blocks'
)}
<br />
{__(
'Would you like to remove any of these meta fields?',
'lazy-blocks'
)}
</p>
<p>{__('Select post meta to remove:', 'lazy-blocks')}</p>
{savedMetaNames.map((currentMeta, index) => (
<ToggleControl
key={currentMeta.metaName}
label={currentMeta.label}
checked={currentMeta.checked}
onChange={(value) => handleMetaToggle(index, value)}
/>
))}
<div className="lzb-gutenberg-remove-post-meta-modal-buttons">
<Button
variant="primary"
onClick={handleConfirmDelete}
disabled={!savedMetaNames.some((m) => m.checked)}
>
{__('Remove selected Meta', 'lazy-blocks')}
</Button>
<Button variant="link" onClick={handleCancel}>
{__('Cancel', 'lazy-blocks')}
</Button>
</div>
</Modal>
);
}

export default RemoveBlockWithSavedMeta;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.lzb-gutenberg-remove-post-meta-modal-buttons {
display: flex;
gap: 16px;
margin-top: 26px;
}
39 changes: 39 additions & 0 deletions assets/hooks/use-all-blocks/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useCallback, useMemo } from '@wordpress/element';
import { useSelect } from '@wordpress/data';

/**
* Custom hook to get all blocks recursively.
*
* @return {Function} Function to get all blocks.
*/
function useAllBlocks() {
const { allBlocks } = useSelect((select) => {
const { getBlocks } = select('core/block-editor');

return {
allBlocks: getBlocks(),
};
}, []);

const flattenBlocks = useCallback((blocks) => {
if (!blocks?.length) {
return [];
}

const result = [];

blocks.forEach((data) => {
result.push(data);

if (data.innerBlocks?.length) {
result.push(...flattenBlocks(data.innerBlocks));
}
});

return result;
}, []);

return useMemo(() => flattenBlocks(allBlocks), [allBlocks, flattenBlocks]);
}

export default useAllBlocks;
2 changes: 1 addition & 1 deletion build/control-file.asset.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?php return array('dependencies' => array('react', 'react-dom', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '5ad81efea32da8e6a67c');
<?php return array('dependencies' => array('react', 'react-dom', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => 'b71835124bf0464c824e');
2 changes: 1 addition & 1 deletion build/control-file.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/editor-constructor.asset.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-plugins'), 'version' => 'edbc2c4cddea46341962');
<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-plugins'), 'version' => 'c9355c9ad4d453a56d82');
2 changes: 1 addition & 1 deletion build/editor-constructor.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions build/editor-rtl.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build/editor.asset.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '61d7c5ab7c461a8c5833');
<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-plugins'), 'version' => 'b410994159c603f84602');
1 change: 1 addition & 0 deletions build/editor.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions build/editor.js

Large diffs are not rendered by default.

0 comments on commit ce1fde8

Please sign in to comment.