From 7c8dc7e3756c6e4b371f74cf996d82acd603ebb3 Mon Sep 17 00:00:00 2001 From: Feoktist Shovchko Date: Fri, 28 Jun 2024 02:10:27 +0300 Subject: [PATCH] feat(uip-editor): store editor state --- src/core/base/root.ts | 11 +++++--- src/plugins/editor/editor-storage.ts | 39 ++++++++++++++++++++++++++++ src/plugins/editor/editor.tsx | 20 ++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/plugins/editor/editor-storage.ts diff --git a/src/core/base/root.ts b/src/core/base/root.ts index 1246374f..5cc1521a 100644 --- a/src/core/base/root.ts +++ b/src/core/base/root.ts @@ -3,9 +3,10 @@ import { memoize, boolAttr, listen, - prop + prop, + attr } from '@exadel/esl/modules/esl-utils/decorators'; - +import {sequentialUID} from '@exadel/esl/modules/esl-utils/misc'; import {UIPStateModel} from './model'; import type {UIPSnippetTemplate} from './snippet'; @@ -33,6 +34,8 @@ export class UIPRoot extends ESLBaseElement { /** CSS query for snippets */ public static SNIPPET_SEL = '[uip-snippet]'; + @attr() public uipId: string = sequentialUID(UIPRoot.is); + /** Indicates that the UIP components' theme is dark */ @boolAttr() public darkTheme: boolean; @@ -50,7 +53,7 @@ export class UIPRoot extends ESLBaseElement { return Array.from(this.querySelectorAll(UIPRoot.SNIPPET_SEL)); } - protected delyedScrollIntoView(): void { + protected delayedScrollIntoView(): void { setTimeout(() => { this.scrollIntoView({behavior: 'smooth', block: 'start'}); }, 100); @@ -64,7 +67,7 @@ export class UIPRoot extends ESLBaseElement { this.$$fire(this.READY_EVENT, {bubbles: false}); if (this.model.anchorSnippet) { - this.delyedScrollIntoView(); + this.delayedScrollIntoView(); } } diff --git a/src/plugins/editor/editor-storage.ts b/src/plugins/editor/editor-storage.ts new file mode 100644 index 00000000..c7668b45 --- /dev/null +++ b/src/plugins/editor/editor-storage.ts @@ -0,0 +1,39 @@ +interface EditorStorageEntry { + ts: string; + data: string; +} + +export class EditorStorage { + public static readonly STORAGE_KEY = 'uip-editor-storage'; + + protected static get(): Record { + return JSON.parse(localStorage.getItem(EditorStorage.STORAGE_KEY) || '{}'); + } + + protected static set(value: Record): void { + localStorage.setItem(EditorStorage.STORAGE_KEY, JSON.stringify(value)); + } + + protected static serializeWithPathname(key: string): string { + return JSON.stringify({path: location.pathname, key}); + } + + public static save(key: string, value: string): void { + const state = {[EditorStorage.serializeWithPathname(key)]: {ts: Date.now(), value}}; + EditorStorage.set(Object.assign(EditorStorage.get(), state)); + } + + public static load(key: string): string | null { + const entry = EditorStorage.get()[EditorStorage.serializeWithPathname(key)] || {} as EditorStorageEntry; + const expirationTime = 3600000 * 12; + if (entry?.ts + expirationTime > Date.now()) return entry.value || null; + EditorStorage.remove(key); + return null; + } + + public static remove(key: string): void { + const data = EditorStorage.get(); + delete data[key]; + EditorStorage.set(data); + } +} diff --git a/src/plugins/editor/editor.tsx b/src/plugins/editor/editor.tsx index 49e19396..8f410db1 100644 --- a/src/plugins/editor/editor.tsx +++ b/src/plugins/editor/editor.tsx @@ -13,6 +13,7 @@ import {attr, boolAttr, decorate, listen, memoize} from '@exadel/esl/modules/esl import {UIPPluginPanel} from '../../core/panel/plugin-panel'; import {CopyIcon} from '../copy/copy-button.icon'; +import {EditorStorage} from './editor-storage'; import {EditorIcon} from './editor.icon'; import type {UIPSnippetsList} from '../snippets-list/snippets-list'; @@ -165,12 +166,31 @@ export class UIPEditor extends UIPPluginPanel { case 'html': if (e && !e.htmlChanges.length) return; this.value = this.model!.html; + if (e && !e.force) this.saveState(); } } + protected saveState(): void { + const key = this.getStateKey(); + if (key && this.value) EditorStorage.save(key, this.value); + } + + protected getStateKey(): string | null { + if (!this.model?.activeSnippet || !this.$root) return null; + return JSON.stringify({html: this.model.activeSnippet.html, id: this.$root.uipId}); + } + /** Handles snippet change to set readonly value */ @listen({event: 'uip:snippet:change', target: ($this: UIPSnippetsList) => $this.$root}) protected _onSnippetChange(): void { this.editable = this.isSnippetEditable; + this.loadState(); + } + + protected loadState(): void { + if (!this.model?.activeSnippet) return; + const key = this.getStateKey(); + const state = key && EditorStorage.load(key); + if (state) this.model.setHtml(state, this); } }