diff --git a/packages/editor-sample/src/App/TemplatePanel/Redo/index.tsx b/packages/editor-sample/src/App/TemplatePanel/Redo/index.tsx
new file mode 100644
index 0000000..2471911
--- /dev/null
+++ b/packages/editor-sample/src/App/TemplatePanel/Redo/index.tsx
@@ -0,0 +1,33 @@
+import React, { useEffect } from 'react';
+
+import { RedoOutlined as RedoIcon } from '@mui/icons-material';
+import { IconButton, Tooltip } from '@mui/material';
+
+import { redo, useCanRedo } from '../../../documents/editor/EditorContext';
+
+export default function Redo() {
+ const canRedo = useCanRedo();
+ useEffect(() => {
+ function handle(event: KeyboardEvent) {
+ if (event.ctrlKey && event.key.toLowerCase() === 'y') {
+ event.preventDefault();
+ redo();
+ }
+ }
+
+ document.addEventListener('keydown', handle);
+ return () => document.removeEventListener('keydown', handle);
+ }, []);
+
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/packages/editor-sample/src/App/TemplatePanel/Undo/index.tsx b/packages/editor-sample/src/App/TemplatePanel/Undo/index.tsx
new file mode 100644
index 0000000..8f2eed6
--- /dev/null
+++ b/packages/editor-sample/src/App/TemplatePanel/Undo/index.tsx
@@ -0,0 +1,33 @@
+import React, { useEffect } from 'react';
+
+import { UndoOutlined as UndoIcon } from '@mui/icons-material';
+import { IconButton, Tooltip } from '@mui/material';
+
+import { undo, useCanUndo } from '../../../documents/editor/EditorContext';
+
+export default function Undo() {
+ const canUndo = useCanUndo();
+ useEffect(() => {
+ function handle(event: KeyboardEvent) {
+ if (event.ctrlKey && event.key.toLowerCase() === 'z') {
+ event.preventDefault();
+ undo();
+ }
+ }
+
+ document.addEventListener('keydown', handle);
+ return () => document.removeEventListener('keydown', handle);
+ }, []);
+
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/packages/editor-sample/src/App/TemplatePanel/index.tsx b/packages/editor-sample/src/App/TemplatePanel/index.tsx
index 0340cad..54c08f8 100644
--- a/packages/editor-sample/src/App/TemplatePanel/index.tsx
+++ b/packages/editor-sample/src/App/TemplatePanel/index.tsx
@@ -20,6 +20,8 @@ import ImportJson from './ImportJson';
import JsonPanel from './JsonPanel';
import MainTabsGroup from './MainTabsGroup';
import ShareButton from './ShareButton';
+import Undo from './Undo';
+import Redo from './Redo';
export default function TemplatePanel() {
const document = useDocument();
@@ -97,6 +99,8 @@ export default function TemplatePanel() {
+
+
diff --git a/packages/editor-sample/src/documents/editor/EditorContext.tsx b/packages/editor-sample/src/documents/editor/EditorContext.tsx
index 0ac60e6..e2e02d2 100644
--- a/packages/editor-sample/src/documents/editor/EditorContext.tsx
+++ b/packages/editor-sample/src/documents/editor/EditorContext.tsx
@@ -1,5 +1,5 @@
import { create } from 'zustand';
-
+import debounce from '@mui/material/utils/debounce';
import getConfiguration from '../../getConfiguration';
import { TEditorConfiguration } from './core';
@@ -7,6 +7,9 @@ import { TEditorConfiguration } from './core';
type TValue = {
document: TEditorConfiguration;
+ documentHistory: Array,
+ documentHistoryIndex: number,
+
selectedBlockId: string | null;
selectedSidebarTab: 'block-configuration' | 'styles';
selectedMainTab: 'editor' | 'preview' | 'json' | 'html';
@@ -18,6 +21,8 @@ type TValue = {
const editorStateStore = create(() => ({
document: getConfiguration(window.location.hash),
+ documentHistory: [],
+ documentHistoryIndex: 0,
selectedBlockId: null,
selectedSidebarTab: 'styles',
selectedMainTab: 'editor',
@@ -76,7 +81,25 @@ export function setSidebarTab(selectedSidebarTab: TValue['selectedSidebarTab'])
return editorStateStore.setState({ selectedSidebarTab });
}
+const addDocumentToHistory = debounce(function (document: TValue['document']) {
+ let documentHistory = editorStateStore.getState().documentHistory;
+ const currentIndex = editorStateStore.getState().documentHistoryIndex;
+ if (currentIndex < documentHistory.length - 1) {
+ documentHistory = documentHistory.slice(0, currentIndex + 1);
+ }
+ documentHistory.push(document);
+ if (documentHistory.length > 254) {
+ documentHistory = documentHistory.slice(1);
+ }
+
+ editorStateStore.setState({
+ documentHistory,
+ documentHistoryIndex: documentHistory.length - 1,
+ });
+}, 500);
+
export function resetDocument(document: TValue['document']) {
+ addDocumentToHistory(document);
return editorStateStore.setState({
document,
selectedSidebarTab: 'styles',
@@ -86,11 +109,47 @@ export function resetDocument(document: TValue['document']) {
export function setDocument(document: TValue['document']) {
const originalDocument = editorStateStore.getState().document;
+ const mergedDocument = {
+ ...originalDocument,
+ ...document,
+ };
+ addDocumentToHistory(mergedDocument);
return editorStateStore.setState({
- document: {
- ...originalDocument,
- ...document,
- },
+ document: mergedDocument,
+ });
+}
+
+export function useCanUndo() {
+ return editorStateStore(s => s.documentHistory.length > 0 && s.documentHistoryIndex > 0);
+}
+
+export function useCanRedo() {
+ return editorStateStore(s => s.documentHistory.length > 0 && s.documentHistoryIndex < s.documentHistory.length - 1);
+}
+
+export function undo() {
+ const documentHistory = editorStateStore.getState().documentHistory;
+ const currentIndex = editorStateStore.getState().documentHistoryIndex;
+ if (documentHistory.length <= 0 || currentIndex <= 0) {
+ return;
+ }
+ const changeToIndex = currentIndex - 1;
+ editorStateStore.setState({
+ document: documentHistory[changeToIndex],
+ documentHistoryIndex: changeToIndex,
+ });
+}
+
+export function redo() {
+ const documentHistory = editorStateStore.getState().documentHistory;
+ const currentIndex = editorStateStore.getState().documentHistoryIndex;
+ if (documentHistory.length <= 0 || currentIndex >= documentHistory.length - 1) {
+ return;
+ }
+ const changeToIndex = currentIndex + 1;
+ editorStateStore.setState({
+ document: documentHistory[changeToIndex],
+ documentHistoryIndex: changeToIndex,
});
}