diff --git a/Gruntfile.js b/Gruntfile.js
index ba2148bac450cb29a393cc28e9a671152391c7b4..ab26eb2cf76c792a9fe7c40aa0db1aab3707da23 100755
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,10 +1,8 @@
-module.exports = function( grunt ) {
-
+module.exports = function ( grunt ) {
 	'use strict';
 
 	// Project configuration
 	grunt.initConfig( {
-
 		pkg: grunt.file.readJSON( 'package.json' ),
 
 		addtextdomain: {
@@ -13,17 +11,24 @@ module.exports = function( grunt ) {
 			},
 			update_all_domains: {
 				options: {
-					updateDomains: true
+					updateDomains: true,
 				},
-				src: [ '*.php', '**/*.php', '!\.git/**/*', '!bin/**/*', '!node_modules/**/*', '!tests/**/*' ]
-			}
+				src: [
+					'*.php',
+					'**/*.php',
+					'!.git/**/*',
+					'!bin/**/*',
+					'!node_modules/**/*',
+					'!tests/**/*',
+				],
+			},
 		},
 
 		wp_readme_to_markdown: {
 			your_target: {
 				files: {
-					'README.md': 'readme.txt'
-				}
+					'README.md': 'readme.txt',
+				},
 			},
 		},
 
@@ -36,21 +41,20 @@ module.exports = function( grunt ) {
 					potFilename: 'wp-revisions-control.pot',
 					potHeaders: {
 						poedit: true,
-						'x-poedit-keywordslist': true
+						'x-poedit-keywordslist': true,
 					},
 					type: 'wp-plugin',
-					updateTimestamp: true
-				}
-			}
+					updateTimestamp: true,
+				},
+			},
 		},
 	} );
 
 	grunt.loadNpmTasks( 'grunt-wp-i18n' );
 	grunt.loadNpmTasks( 'grunt-wp-readme-to-markdown' );
-	grunt.registerTask( 'default', [ 'i18n','readme' ] );
-	grunt.registerTask( 'i18n', ['addtextdomain', 'makepot'] );
-	grunt.registerTask( 'readme', ['wp_readme_to_markdown'] );
+	grunt.registerTask( 'default', [ 'i18n', 'readme' ] );
+	grunt.registerTask( 'i18n', [ 'addtextdomain', 'makepot' ] );
+	grunt.registerTask( 'readme', [ 'wp_readme_to_markdown' ] );
 
 	grunt.util.linefeed = '\n';
-
 };
diff --git a/assets/build/classic-editor.asset.php b/assets/build/classic-editor.asset.php
index 160288e92ecaa28616f4219080c0dc33701aac82..11808cbbe06687c0a53024a4cb5ef95baae52bc3 100644
--- a/assets/build/classic-editor.asset.php
+++ b/assets/build/classic-editor.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('jquery'), 'version' => '8269d3a15b570e3468eb');
+<?php return array('dependencies' => array('jquery'), 'version' => '60fb0f1e9e40b24c7794');
diff --git a/assets/build/classic-editor.js b/assets/build/classic-editor.js
index 9382542d63619e67fa9c8dbba16f0c22bbcb65e2..72baaa6bf5890260a068560fc0b9f4f4ac011707 100644
--- a/assets/build/classic-editor.js
+++ b/assets/build/classic-editor.js
@@ -1 +1 @@
-!function(){"use strict";var t,e={n:function(t){var o=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(o,{a:o}),o},d:function(t,o){for(var n in o)e.o(o,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:o[n]})},o:function(t,e){return Object.prototype.hasOwnProperty.call(t,e)}},o=window.jQuery;(t=e.n(o)())(document).ready((function(){var e,o=t("#"+wp_revisions_control.namespace+" .button.purge"),n=null;function r(n){if(n.error)alert(n.error),t(o).text(e);else if(n.success){var r=t("ul.post-revisions > li");t(r).each((function(){t(this).text().match(wp_revisions_control.autosave)||t(this).slideUp("slow").remove()})),t(o).fadeOut("slow").after(wp_revisions_control.nothing_text)}}function i(){alert(wp_revisions_control.error),t(o).text(e)}e=t(o).text(),t(o).on("click",(function(){n=parseInt(t(this).data("postid")),t(o).text(wp_revisions_control.processing_text),confirm(wp_revisions_control.ays)&&n?t.ajax({url:ajaxurl,cache:!1,data:{action:wp_revisions_control.action_base+"_purge",post_id:n,nonce:t(this).data("nonce")},type:"post",dataType:"json",success:r,error:i}):t(o).text(e)}))}))}();
\ No newline at end of file
+!function(){"use strict";var t,e={n:function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,{a:n}),n},d:function(t,n){for(var o in n)e.o(n,o)&&!e.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:n[o]})},o:function(t,e){return Object.prototype.hasOwnProperty.call(t,e)}},n=window.jQuery;(t=e.n(n)())(document).ready((function(){const e=t("#"+wp_revisions_control.namespace+" .button.purge");let n=null,o=null;function r(o){if(o.error)alert(o.error),t(e).text(n);else if(o.success){const n=t("ul.post-revisions > li");t(n).each((function(){t(this).text().match(wp_revisions_control.autosave)||t(this).slideUp("slow").remove()})),t(e).fadeOut("slow").after(wp_revisions_control.nothing_text)}}function s(){alert(wp_revisions_control.error),t(e).text(n)}n=t(e).text(),t(e).on("click",(function(){o=parseInt(t(this).data("postid")),t(e).text(wp_revisions_control.processing_text),confirm(wp_revisions_control.ays)&&o?t.ajax({url:ajaxurl,cache:!1,data:{action:wp_revisions_control.action_base+"_purge",post_id:o,nonce:t(this).data("nonce")},type:"post",dataType:"json",success:r,error:s}):t(e).text(n)}))}))}();
\ No newline at end of file
diff --git a/assets/build/gutenberg.asset.php b/assets/build/gutenberg.asset.php
index 704609745aeef47961b56ebc7fbed604d0d15666..024960ba7a9dd6963bddbddd1e92f2772c270678 100644
--- a/assets/build/gutenberg.asset.php
+++ b/assets/build/gutenberg.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-edit-post', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => '75f14aee54cc9b52ebe9');
+<?php return array('dependencies' => array('wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-edit-post', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => 'caf115ed5017b4ad3c90');
diff --git a/assets/build/gutenberg.js b/assets/build/gutenberg.js
index 4c7a9397b45822ba9bbe3b87f101dabdb2822769..ef44797dd1f679b344d9ebc067481ff6724aa5af 100644
--- a/assets/build/gutenberg.js
+++ b/assets/build/gutenberg.js
@@ -1,3 +1,3 @@
-!function(){"use strict";var e={989:function(e){e.exports=window.wp.apiFetch},609:function(e){e.exports=window.wp.components},333:function(e){e.exports=window.wp.compose},818:function(e){e.exports=window.wp.data},67:function(e){e.exports=window.wp.editPost},307:function(e){e.exports=window.wp.element},736:function(e){e.exports=window.wp.i18n},817:function(e){e.exports=window.wp.plugins}},t={};function o(n){var r=t[n];if(void 0!==r)return r.exports;var i=t[n]={exports:{}};return e[n](i,i.exports,o),i.exports}o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,{a:t}),t},o.d=function(e,t){for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e=o(307);const t=o(989),{Button:n,Modal:r,TextControl:i}=o(609),{compose:s}=o(333),{withSelect:c,withDispatch:l}=o(818),{PluginDocumentSettingPanel:u}=o(67),{useState:a}=o(307),{__:__,_n:_n,sprintf:p}=o(736),{registerPlugin:w}=o(817),d="_wp_rev_ctl_limit",v="wp-revisions-control",m=s([c((e=>{const{getCurrentPostRevisionsCount:t,getEditedPostAttribute:o}=e("core/editor"),n=t(),r=o("meta")[d];return{limit:r,showPurgeButton:Boolean(r)&&n>parseInt(r)}})),l(((e,o,n)=>{let{limit:r}=o,{select:i}=n;return{manualPurge:()=>{const o=i("core/editor").getCurrentPostId();t({path:`/wp-revisions-control/v1/schedule/${o}`,method:"PUT"}).then((t=>{let o,n;t?(o="success",n=__("Excess revisions scheduled for removal.","wp_revisions_control")):(o="error",n=__("Failed to schedule excess revisions for removal.","wp_revisions_control")),e("core/notices").createNotice(o,n,{id:"wp-revisions-control-scheduled-purge",isDismissible:!0,type:"snackbar"})}))},updateMeta:t=>{e("core/editor").editPost({meta:{[d]:t}})}}}))])((t=>{let{limit:o,manualPurge:s,showPurgeButton:c,updateMeta:l}=t;return(0,e.createElement)(u,{name:v,title:__("WP Revisions Control","wp_revisions_control"),className:v},(0,e.createElement)(i,{label:__("Number of revisions to retain:","wp_revisions_control"),help:__("Leave blank to keep all revisions.","wp_revisions_control"),value:o,onChange:l}),c&&((t,o)=>{const[i,s]=a(!1),c=()=>s(!1),l=p(
+!function(){"use strict";var e={989:function(e){e.exports=window.wp.apiFetch},609:function(e){e.exports=window.wp.components},333:function(e){e.exports=window.wp.compose},818:function(e){e.exports=window.wp.data},67:function(e){e.exports=window.wp.editPost},307:function(e){e.exports=window.wp.element},736:function(e){e.exports=window.wp.i18n},817:function(e){e.exports=window.wp.plugins}},t={};function o(n){var r=t[n];if(void 0!==r)return r.exports;var i=t[n]={exports:{}};return e[n](i,i.exports,o),i.exports}o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,{a:t}),t},o.d=function(e,t){for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e=o(307);const t=o(989),{Button:n,Modal:r,TextControl:i}=o(609),{compose:s}=o(333),{withSelect:l,withDispatch:c}=o(818),{PluginDocumentSettingPanel:a}=o(67),{useState:u}=o(307),{__:__,_n:_n,sprintf:p}=o(736),{registerPlugin:w}=o(817),d=window.wpRevisionsControlBlockEditorSettings.metaKey,v="wp-revisions-control",m=s([l((e=>{const{getCurrentPostRevisionsCount:t,getEditedPostAttribute:o}=e("core/editor"),n=t(),r=o("meta")[d];return{limit:r,showPurgeButton:Boolean(r)&&n>parseInt(r)}})),c(((e,o,n)=>{let{}=o,{select:r}=n;return{manualPurge:()=>{const o=r("core/editor").getCurrentPostId();t({path:`/wp-revisions-control/v1/schedule/${o}`,method:"PUT"}).then((t=>{let o,n;t?(o="success",n=__("Excess revisions scheduled for removal.","wp_revisions_control")):(o="error",n=__("Failed to schedule excess revisions for removal.","wp_revisions_control")),e("core/notices").createNotice(o,n,{id:"wp-revisions-control-scheduled-purge",isDismissible:!0,type:"snackbar"})}))},updateMeta:t=>{e("core/editor").editPost({meta:{[d]:t}})}}}))])((t=>{let{limit:o,manualPurge:s,showPurgeButton:l,updateMeta:c}=t;return(0,e.createElement)(a,{name:v,title:__("WP Revisions Control","wp_revisions_control"),className:v},(0,e.createElement)(i,{label:__("Number of revisions to retain:","wp_revisions_control"),help:__("Leave blank to keep all revisions.","wp_revisions_control"),value:o,onChange:c}),l&&((t,o)=>{const[i,s]=u(!1),l=()=>s(!1),c=parseInt(t,10),a=0===c?__("This will remove all revisions.","wp_revisions_control"):p(
 /* translators: 1. Number of revisions to keep. */
-_n("This will remove all but the most-recent revision.","This will remove all but the %1$d most-recent revisions.",parseInt(t),"wp_revisions_control"),t);return(0,e.createElement)(e.Fragment,null,(0,e.createElement)(n,{isSecondary:!0,onClick:()=>s(!0)},__("Purge excess revisions","wp_revisions_control")),i&&(0,e.createElement)(r,{title:__("Purge excess revisions","wp_revisions_control"),contentLabel:l,onRequestClose:c},(0,e.createElement)("p",null,l),(0,e.createElement)(n,{isSecondary:!0,onClick:c},__("Cancel","wp_revisions_control")),(0,e.createElement)(n,{isPrimary:!0,onClick:()=>{c(),o()}},__("Purge","wp_revisions_control"))))})(o,s))}));w(v,{render:m,icon:"backup"})}()}();
\ No newline at end of file
+_n("This will remove all but the most-recent revision.","This will remove all but the %1$d most-recent revisions.",c,"wp_revisions_control"),t);return(0,e.createElement)(e.Fragment,null,(0,e.createElement)(n,{isSecondary:!0,onClick:()=>s(!0)},__("Purge excess revisions","wp_revisions_control")),i&&(0,e.createElement)(r,{title:__("Purge excess revisions","wp_revisions_control"),contentLabel:a,onRequestClose:l},(0,e.createElement)("p",null,a),(0,e.createElement)(n,{isPrimary:!0,onClick:()=>{l(),o()}},__("Purge","wp_revisions_control"))," ",(0,e.createElement)(n,{isSecondary:!0,onClick:l},__("Cancel","wp_revisions_control"))))})(o,s))}));w(v,{render:m,icon:"backup"})}()}();
\ No newline at end of file
diff --git a/assets/src/classic-editor.js b/assets/src/classic-editor.js
index 86237bc82ac2fe4e843983dc96f3008975baa6d7..c20d89c1287db35ff5c253524e2e5147e91a2d1c 100644
--- a/assets/src/classic-editor.js
+++ b/assets/src/classic-editor.js
@@ -1,11 +1,16 @@
+/* global ajaxurl, alert, confirm, wp_revisions_control */
+/* eslint-disable camelcase */
+
+// eslint-disable-next-line import/no-extraneous-dependencies
 import jQuery from 'jquery';
 
-( function( $ ) {
-	$( document ).ready( function() {
-		var purge_button = $( '#' + wp_revisions_control.namespace + ' .button.purge' ),
-			post_id      = null,
-			nonce        = null,
-			button_text  = null;
+( function ( $ ) {
+	$( document ).ready( function () {
+		const purge_button = $(
+			'#' + wp_revisions_control.namespace + ' .button.purge'
+		);
+		let button_text = null,
+			post_id = null;
 
 		button_text = $( purge_button ).text();
 
@@ -19,22 +24,23 @@ import jQuery from 'jquery';
 
 			$( purge_button ).text( wp_revisions_control.processing_text );
 
-			var confirmed = confirm( wp_revisions_control.ays );
+			// eslint-disable-next-line no-alert
+			const confirmed = confirm( wp_revisions_control.ays );
 
 			if ( confirmed && post_id ) {
-				$.ajax({
+				$.ajax( {
 					url: ajaxurl,
 					cache: false,
 					data: {
 						action: wp_revisions_control.action_base + '_purge',
-						post_id: post_id,
-						nonce: $( this ).data( 'nonce' )
+						post_id,
+						nonce: $( this ).data( 'nonce' ),
 					},
 					type: 'post',
 					dataType: 'json',
 					success: ajax_purge_request_success,
-					error: ajax_purge_request_error
-				});
+					error: ajax_purge_request_error,
+				} );
 			} else {
 				$( purge_button ).text( button_text );
 			}
@@ -43,23 +49,31 @@ import jQuery from 'jquery';
 		/**
 		 * User feedback when Ajax request succeeds
 		 * Does not indicate that purge request succeeded
+		 *
+		 * @param {Object} response Ajax response.
 		 */
 		function ajax_purge_request_success( response ) {
 			if ( response.error ) {
+				// eslint-disable-next-line no-alert
 				alert( response.error );
 
 				$( purge_button ).text( button_text );
 			} else if ( response.success ) {
-				var list_table = $( 'ul.post-revisions > li' );
+				const list_table = $( 'ul.post-revisions > li' );
 
-				$( list_table ).each( function() {
-					var autosave = $( this ).text().match( wp_revisions_control.autosave );
+				$( list_table ).each( function () {
+					const autosave = $( this )
+						.text()
+						.match( wp_revisions_control.autosave );
 
-					if ( ! autosave )
+					if ( ! autosave ) {
 						$( this ).slideUp( 'slow' ).remove();
+					}
 				} );
 
-				$( purge_button ).fadeOut( 'slow' ).after( wp_revisions_control.nothing_text );
+				$( purge_button )
+					.fadeOut( 'slow' )
+					.after( wp_revisions_control.nothing_text );
 			}
 		}
 
@@ -67,6 +81,7 @@ import jQuery from 'jquery';
 		 * Return a generic error when the Ajax request fails
 		 */
 		function ajax_purge_request_error() {
+			// eslint-disable-next-line no-alert
 			alert( wp_revisions_control.error );
 
 			$( purge_button ).text( button_text );
diff --git a/assets/src/gutenberg.js b/assets/src/gutenberg.js
index 0e1949f527f4059dd8b37f719845335202e75cb5..ac847f9f9a674afb9861c0d95b82402b2c9a7e4b 100644
--- a/assets/src/gutenberg.js
+++ b/assets/src/gutenberg.js
@@ -7,9 +7,21 @@ const { useState } = require( '@wordpress/element' );
 const { __, _n, sprintf } = require( '@wordpress/i18n' );
 const { registerPlugin } = require( '@wordpress/plugins' );
 
-const metaKey = '_wp_rev_ctl_limit';
+const metaKey = window.wpRevisionsControlBlockEditorSettings.metaKey;
 const slug = 'wp-revisions-control';
 
+/**
+ * The settings panel for the plugin.
+ *
+ * @param {Object}   props                 Component props.
+ * @param {number}   props.limit           Number of revisions to keep.
+ * @param {Function} props.manualPurge     Callback to manually purge revisions.
+ * @param {boolean}  props.showPurgeButton Whether there are enough revisions to
+ *                                         show the purge button.
+ * @param {Function} props.updateMeta      Callback to update the revisions-limit for
+ *                                         this post.
+ * @return {JSX.Element} Sidebar panel.
+ */
 const Render = ( { limit, manualPurge, showPurgeButton, updateMeta } ) => (
 	<PluginDocumentSettingPanel
 		name={ slug }
@@ -17,8 +29,14 @@ const Render = ( { limit, manualPurge, showPurgeButton, updateMeta } ) => (
 		className={ slug }
 	>
 		<TextControl
-			label={ __( 'Number of revisions to retain:', 'wp_revisions_control' ) }
-			help={ __( 'Leave blank to keep all revisions.', 'wp_revisions_control' ) }
+			label={ __(
+				'Number of revisions to retain:',
+				'wp_revisions_control'
+			) }
+			help={ __(
+				'Leave blank to keep all revisions.',
+				'wp_revisions_control'
+			) }
 			value={ limit }
 			onChange={ updateMeta }
 		/>
@@ -27,6 +45,14 @@ const Render = ( { limit, manualPurge, showPurgeButton, updateMeta } ) => (
 	</PluginDocumentSettingPanel>
 );
 
+/**
+ * Modal to confirm and trigger manual purge of excess revisions.
+ *
+ * @param {number}   limit       Number of revisions to keep.
+ * @param {Function} manualPurge Callback to manually purge revisions.
+ * @return {JSX.Element} Modal to confirm and trigger manual purge of excess
+ *                       revisions.
+ */
 const PurgeModal = ( limit, manualPurge ) => {
 	const [ isOpen, setOpen ] = useState( false );
 	const openModal = () => setOpen( true );
@@ -37,18 +63,20 @@ const PurgeModal = ( limit, manualPurge ) => {
 	};
 	const parsedLimit = parseInt( limit, 10 );
 
-	const modalText = 0 === parsedLimit
-		? __( 'This will remove all revisions.', 'wp_revisions_control' )
-		: sprintf(
-			/* translators: 1. Number of revisions to keep. */
-			_n(
-				'This will remove all but the most-recent revision.',
-				'This will remove all but the %1$d most-recent revisions.',
-				parsedLimit,
-				'wp_revisions_control'
-			),
-			limit
-		);
+	const modalText =
+		0 === parsedLimit
+			? __( 'This will remove all revisions.', 'wp_revisions_control' )
+			: // eslint-disable-next-line @wordpress/valid-sprintf
+			  sprintf(
+					/* translators: 1. Number of revisions to keep. */
+					_n(
+						'This will remove all but the most-recent revision.',
+						'This will remove all but the %1$d most-recent revisions.',
+						parsedLimit,
+						'wp_revisions_control'
+					),
+					limit
+			  );
 
 	return (
 		<>
@@ -58,20 +86,18 @@ const PurgeModal = ( limit, manualPurge ) => {
 
 			{ isOpen && (
 				<Modal
-					title={ __( 'Purge excess revisions', 'wp_revisions_control' ) }
+					title={ __(
+						'Purge excess revisions',
+						'wp_revisions_control'
+					) }
 					contentLabel={ modalText }
 					onRequestClose={ closeModal }
 				>
-					<p>
-						{ modalText }
-					</p>
-
+					<p>{ modalText }</p>
 					<Button isPrimary onClick={ closeModalAndPurge }>
 						{ __( 'Purge', 'wp_revisions_control' ) }
 					</Button>
-
 					&nbsp;
-
 					<Button isSecondary onClick={ closeModal }>
 						{ __( 'Cancel', 'wp_revisions_control' ) }
 					</Button>
@@ -81,80 +107,77 @@ const PurgeModal = ( limit, manualPurge ) => {
 	);
 };
 
-// TODO: switch to `useSelect` and `useDispatch`.
-const RevisionsControl = compose(
-	[
-		withSelect( ( select ) => {
-			const {
-				getCurrentPostRevisionsCount,
-				getEditedPostAttribute,
-			} = select( 'core/editor' );
-
-			const count = getCurrentPostRevisionsCount();
-			const limit = getEditedPostAttribute(
-				'meta'
-			)[ metaKey ];
-
-			const showPurgeButton = Boolean( limit )
-				&& count > parseInt( limit );
-
-			return {
-				limit,
-				showPurgeButton,
-			};
-		} ),
-		withDispatch( ( dispatch, { limit }, { select } ) => {
-			const manualPurge = () => {
-				const postId = select( 'core/editor' ).getCurrentPostId();
-
-				apiFetch( {
-					path: `/wp-revisions-control/v1/schedule/${postId}`,
-					method: 'PUT',
-				} )
-					.then( ( result ) => {
-						let noticeType;
-						let noticeText;
-
-						if ( result ) {
-							noticeType = 'success';
-							noticeText = __( 'Excess revisions scheduled for removal.', 'wp_revisions_control' );
-						} else {
-							noticeType = 'error';
-							noticeText = __( 'Failed to schedule excess revisions for removal.', 'wp_revisions_control' );
-						}
-
-						dispatch( 'core/notices' ).createNotice(
-							noticeType,
-							noticeText,
-							{
-								id: 'wp-revisions-control-scheduled-purge',
-								isDismissible: true,
-								type: 'snackbar',
-							}
-						);
-					} );
-			};
-
-			const updateMeta = ( value ) => {
-				dispatch( 'core/editor' ).editPost( {
-					meta: {
-						[ metaKey ]: value,
-					},
-				} );
-			};
-
-			return {
-				manualPurge,
-				updateMeta,
-			};
-		} ),
-	]
-)( Render );
-
-registerPlugin(
-	slug,
-	{
-		render: RevisionsControl,
-		icon: 'backup',
-	}
-);
+/**
+ * Higher order component to render plugin's sidebar panel.
+ */
+const RevisionsControl = compose( [
+	withSelect( ( select ) => {
+		const { getCurrentPostRevisionsCount, getEditedPostAttribute } =
+			select( 'core/editor' );
+
+		const count = getCurrentPostRevisionsCount();
+		const limit = getEditedPostAttribute( 'meta' )[ metaKey ];
+
+		const showPurgeButton = Boolean( limit ) && count > parseInt( limit );
+
+		return {
+			limit,
+			showPurgeButton,
+		};
+	} ),
+	withDispatch( ( dispatch, {}, { select } ) => {
+		const manualPurge = () => {
+			const postId = select( 'core/editor' ).getCurrentPostId();
+
+			apiFetch( {
+				path: `/wp-revisions-control/v1/schedule/${ postId }`,
+				method: 'PUT',
+			} ).then( ( result ) => {
+				let noticeType;
+				let noticeText;
+
+				if ( result ) {
+					noticeType = 'success';
+					noticeText = __(
+						'Excess revisions scheduled for removal.',
+						'wp_revisions_control'
+					);
+				} else {
+					noticeType = 'error';
+					noticeText = __(
+						'Failed to schedule excess revisions for removal.',
+						'wp_revisions_control'
+					);
+				}
+
+				dispatch( 'core/notices' ).createNotice(
+					noticeType,
+					noticeText,
+					{
+						id: 'wp-revisions-control-scheduled-purge',
+						isDismissible: true,
+						type: 'snackbar',
+					}
+				);
+			} );
+		};
+
+		const updateMeta = ( value ) => {
+			dispatch( 'core/editor' ).editPost( {
+				meta: {
+					[ metaKey ]: value,
+				},
+			} );
+		};
+
+		return {
+			manualPurge,
+			updateMeta,
+		};
+	} ),
+] )( Render );
+
+registerPlugin( slug, {
+	render: RevisionsControl,
+	icon: 'backup',
+} );
diff --git a/inc/class-block-editor.php b/inc/class-block-editor.php
index 261090faa6a068b2c5362a510656464692db60fc..d9c428e565122cb3a4f37017d3e2d4b18dc71c5b 100644
--- a/inc/class-block-editor.php
+++ b/inc/class-block-editor.php
@@ -33,7 +33,7 @@ class Block_Editor {
 	private function setup() {
 		add_action( 'rest_api_init', array( $this, 'action_rest_api_init' ) );
 		add_filter( 'is_protected_meta', array( $this, 'filter_is_protected_meta' ), 10, 2 );
-		add_action( $this->cron_action, array( $this, 'do_purge_excess' ) );
+		add_action( $this->cron_action, array( WP_Revisions_Control::get_instance(), 'do_purge_excess' ) );
 		add_action( 'admin_init', array( $this, 'action_admin_init' ) );
 	}
 
@@ -54,10 +54,7 @@ class Block_Editor {
 	 * Register REST API components for Gutenberg UI.
 	 */
 	public function action_rest_api_init() {
-		if (
-			! function_exists( 'register_meta' )
-			|| ! function_exists( 'register_rest_route' )
-		) {
+		if ( ! function_exists( 'register_rest_route' ) ) {
 			return;
 		}
 
@@ -158,10 +155,11 @@ class Block_Editor {
 	 * Register Gutenberg script.
 	 */
 	public function action_enqueue_block_editor_assets() {
+		$handle     = 'wp-revisions-control-block-editor';
 		$asset_data = require_once dirname( __DIR__ ) . '/assets/build/gutenberg.asset.php';
 
 		wp_enqueue_script(
-			'wp-revisions-control-block-editor',
+			$handle,
 			plugins_url(
 				'assets/build/gutenberg.js',
 				__DIR__
@@ -169,5 +167,13 @@ class Block_Editor {
 			$asset_data['dependencies'],
 			$asset_data['version']
 		);
+
+		wp_localize_script(
+			$handle,
+			'wpRevisionsControlBlockEditorSettings',
+			array(
+				'metaKey' => WP_REVISIONS_CONTROL_LIMIT_META_KEY,
+			)
+		);
 	}
 }
diff --git a/languages/wp-revisions-control.pot b/languages/wp-revisions-control.pot
index 8ee4a21d6da9ab28e615ad55e27dfe10a06ae1fe..8dbb176db3e8741fceeb6d73c0f39001176a01cc 100644
--- a/languages/wp-revisions-control.pot
+++ b/languages/wp-revisions-control.pot
@@ -5,7 +5,7 @@ msgstr ""
 "Project-Id-Version: WP Revisions Control 1.4\n"
 "Report-Msgid-Bugs-To: "
 "https://wordpress.org/support/plugin/wp-revisions-control\n"
-"POT-Creation-Date: 2022-06-05 21:22:10+00:00\n"
+"POT-Creation-Date: 2022-06-05 22:13:00+00:00\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=utf-8\n"
 "Content-Transfer-Encoding: 8bit\n"
@@ -25,7 +25,7 @@ msgstr ""
 "X-Textdomain-Support: yes\n"
 "X-Generator: grunt-wp-i18n 1.0.3\n"
 
-#: inc/class-block-editor.php:74
+#: inc/class-block-editor.php:71
 msgid "Number of revisions to retain."
 msgstr ""
 
diff --git a/package-lock.json b/package-lock.json
index 381a8963a205c757fd540796d95f6945f0a04725..835b1c078d3c5fda41bea60e64db88ed13ba8975 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,8 @@
 				"@wordpress/scripts": "^23.2.0",
 				"grunt": "^1.5.3",
 				"grunt-wp-i18n": "^1.0.3",
-				"grunt-wp-readme-to-markdown": "^2.1.0"
+				"grunt-wp-readme-to-markdown": "^2.1.0",
+				"prettier": "npm:wp-prettier@^2.6.2"
 			}
 		},
 		"node_modules/@ampproject/remapping": {
@@ -12093,10 +12094,11 @@
 			}
 		},
 		"node_modules/prettier": {
+			"name": "wp-prettier",
 			"version": "2.6.2",
+			"resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.6.2.tgz",
+			"integrity": "sha512-AV33EzqiFJ3fj+mPlKABN59YFPReLkDxQnj067Z3uEOeRQf3g05WprL0RDuqM7UBhSRo9W1rMSC2KvZmjE5UOA==",
 			"dev": true,
-			"license": "MIT",
-			"peer": true,
 			"bin": {
 				"prettier": "bin-prettier.js"
 			},
@@ -23010,9 +23012,10 @@
 			"dev": true
 		},
 		"prettier": {
-			"version": "2.6.2",
-			"dev": true,
-			"peer": true
+			"version": "npm:wp-prettier@2.6.2",
+			"resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.6.2.tgz",
+			"integrity": "sha512-AV33EzqiFJ3fj+mPlKABN59YFPReLkDxQnj067Z3uEOeRQf3g05WprL0RDuqM7UBhSRo9W1rMSC2KvZmjE5UOA==",
+			"dev": true
 		},
 		"prettier-linter-helpers": {
 			"version": "1.0.0",
diff --git a/package.json b/package.json
index 42c19e92f458d9fde4b019eb0257da8abe7a7885..7d59d7845ce69efa2e4fb6e473d396356ffaf7e1 100755
--- a/package.json
+++ b/package.json
@@ -8,7 +8,8 @@
 		"@wordpress/scripts": "^23.2.0",
 		"grunt": "^1.5.3",
 		"grunt-wp-i18n": "^1.0.3",
-		"grunt-wp-readme-to-markdown": "^2.1.0"
+		"grunt-wp-readme-to-markdown": "^2.1.0",
+		"prettier": "npm:wp-prettier@^2.6.2"
 	},
 	"scripts": {
 		"build": "wp-scripts build",
diff --git a/webpack.config.js b/webpack.config.js
index f8f037ccfbf935d59a677faf38c7630e9b8d6175..63aeaab17f4313a3aa6bbb191078e202b4a9a51b 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,7 +3,7 @@ const { resolve } = require( 'path' );
 
 config.entry = {
 	'classic-editor': './assets/src/classic-editor.js',
-	'gutenberg': './assets/src/gutenberg.js',
+	gutenberg: './assets/src/gutenberg.js',
 };
 
 config.output.path = resolve( process.cwd(), 'assets/build' );