@elementor/editor-documents 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ import { getV1DocumentsManager } from '../utils';
2
+
3
+ /**
4
+ * This test exists only because this function is being used only inside event handlers,
5
+ * and jest can't catch errors that are being thrown. Despite that, we need to test this
6
+ * specific behavior to make sure that we don't break the whole app when V1 isn't available.
7
+ * All of the other logic is tested as an integration test in the `sync-store.test.ts` file,
8
+ * while this one is a unit test.
9
+ *
10
+ * @see https://github.com/testing-library/react-testing-library/issues/624
11
+ */
12
+ describe( '@elementor/editor-documents - Sync Utils', () => {
13
+ it( 'should throw when V1 documents manager is not available', () => {
14
+ // Act & Assert.
15
+ expect( () => {
16
+ getV1DocumentsManager();
17
+ } ).toThrow( 'Elementor Editor V1 documents manager not found' );
18
+ } );
19
+ } );
@@ -0,0 +1 @@
1
+ export { syncStore } from './sync-store';
@@ -0,0 +1,115 @@
1
+ import { Slice } from '../store';
2
+ import { Document } from '../types';
3
+ import { dispatch } from '@elementor/store';
4
+ import { normalizeV1Document, getV1DocumentsManager } from './utils';
5
+ import {
6
+ commandEndEvent,
7
+ CommandEvent,
8
+ commandStartEvent,
9
+ ListenerEvent,
10
+ listenTo,
11
+ v1ReadyEvent,
12
+ } from '@elementor/editor-v1-adapters';
13
+
14
+ export function syncStore( slice: Slice ) {
15
+ syncInitialization( slice );
16
+ syncActiveDocument( slice );
17
+ syncOnDocumentSave( slice );
18
+ syncOnDocumentChange( slice );
19
+ }
20
+
21
+ function syncInitialization( slice: Slice ) {
22
+ const { init } = slice.actions;
23
+
24
+ listenTo(
25
+ v1ReadyEvent(),
26
+ () => {
27
+ const documentsManager = getV1DocumentsManager();
28
+
29
+ const entities = Object.entries( documentsManager.documents )
30
+ .reduce( ( acc: Record<string, Document>, [ id, document ] ) => {
31
+ acc[ id ] = normalizeV1Document( document );
32
+
33
+ return acc;
34
+ }, {} );
35
+
36
+ dispatch( init( {
37
+ entities,
38
+ hostId: documentsManager.getInitialId(),
39
+ activeId: documentsManager.getCurrentId(),
40
+ } ) );
41
+ }
42
+ );
43
+ }
44
+
45
+ function syncActiveDocument( slice: Slice ) {
46
+ const { activateDocument } = slice.actions;
47
+
48
+ listenTo(
49
+ commandEndEvent( 'editor/documents/open' ),
50
+ () => {
51
+ const documentsManager = getV1DocumentsManager();
52
+ const currentDocument = normalizeV1Document( documentsManager.getCurrent() );
53
+
54
+ dispatch( activateDocument( currentDocument ) );
55
+ }
56
+ );
57
+ }
58
+
59
+ function syncOnDocumentSave( slice: Slice ) {
60
+ const { startSaving, endSaving, startSavingDraft, endSavingDraft } = slice.actions;
61
+
62
+ const isDraft = ( e: ListenerEvent ) => {
63
+ const event = e as CommandEvent<{ status: string }>;
64
+
65
+ /**
66
+ * @see https://github.com/elementor/elementor/blob/5f815d40a/assets/dev/js/editor/document/save/hooks/ui/save/before.js#L18-L22
67
+ */
68
+ return event.args?.status === 'autosave';
69
+ };
70
+
71
+ listenTo(
72
+ commandStartEvent( 'document/save/save' ),
73
+ ( e ) => {
74
+ if ( isDraft( e ) ) {
75
+ dispatch( startSavingDraft() );
76
+ return;
77
+ }
78
+
79
+ dispatch( startSaving() );
80
+ }
81
+ );
82
+
83
+ listenTo(
84
+ commandEndEvent( 'document/save/save' ),
85
+ ( e ) => {
86
+ const activeDocument = normalizeV1Document(
87
+ getV1DocumentsManager().getCurrent()
88
+ );
89
+
90
+ if ( isDraft( e ) ) {
91
+ dispatch( endSavingDraft( activeDocument ) );
92
+ } else {
93
+ dispatch( endSaving( activeDocument ) );
94
+ }
95
+ }
96
+ );
97
+ }
98
+
99
+ function syncOnDocumentChange( slice: Slice ) {
100
+ const { markAsDirty, markAsPristine } = slice.actions;
101
+
102
+ listenTo(
103
+ commandEndEvent( 'document/save/set-is-modified' ),
104
+ () => {
105
+ const currentDocument = getV1DocumentsManager().getCurrent();
106
+
107
+ if ( currentDocument.editor.isChanged ) {
108
+ dispatch( markAsDirty() );
109
+ return;
110
+ }
111
+
112
+ dispatch( markAsPristine() );
113
+ }
114
+ );
115
+ }
@@ -0,0 +1,35 @@
1
+ import { Document, ExtendedWindow, V1Document } from '../types';
2
+
3
+ export function getV1DocumentsManager() {
4
+ const documentsManager = ( window as unknown as ExtendedWindow ).elementor?.documents;
5
+
6
+ if ( ! documentsManager ) {
7
+ throw new Error( 'Elementor Editor V1 documents manager not found' );
8
+ }
9
+
10
+ return documentsManager;
11
+ }
12
+
13
+ export function normalizeV1Document( documentData: V1Document ): Document {
14
+ // Draft or autosave.
15
+ const isUnpublishedRevision = documentData.config.revisions.current_id !== documentData.id;
16
+
17
+ return {
18
+ id: documentData.id,
19
+ title: documentData.container.settings.get( 'post_title' ),
20
+ type: {
21
+ value: documentData.config.type,
22
+ label: documentData.config.panel.title,
23
+ },
24
+ status: {
25
+ value: documentData.config.status.value,
26
+ label: documentData.config.status.label,
27
+ },
28
+ isDirty: documentData.editor.isChanged || isUnpublishedRevision,
29
+ isSaving: documentData.editor.isSaving,
30
+ isSavingDraft: false,
31
+ userCan: {
32
+ publish: documentData.config.user.can_publish,
33
+ },
34
+ };
35
+ }
package/src/types.ts ADDED
@@ -0,0 +1,62 @@
1
+ export type Document = {
2
+ id: number,
3
+ title: string,
4
+ type: {
5
+ value: string,
6
+ label: string,
7
+ },
8
+ status: {
9
+ value: string,
10
+ label: string,
11
+ },
12
+ isDirty: boolean,
13
+ isSaving: boolean,
14
+ isSavingDraft: boolean,
15
+ userCan: {
16
+ publish?: boolean,
17
+ },
18
+ };
19
+
20
+ export type ExtendedWindow = Window & {
21
+ elementor: {
22
+ documents: {
23
+ documents: Record<string, V1Document>,
24
+ getCurrentId: () => number,
25
+ getInitialId: () => number,
26
+ getCurrent: () => V1Document,
27
+ }
28
+ }
29
+ }
30
+
31
+ export type V1Document = {
32
+ id: number,
33
+ config: {
34
+ type: string,
35
+ user: {
36
+ can_publish: boolean,
37
+ },
38
+ revisions: {
39
+ current_id: number,
40
+ }
41
+ panel: {
42
+ title: string,
43
+ }
44
+ status: {
45
+ value: string,
46
+ label: string,
47
+ }
48
+ },
49
+ editor: {
50
+ isChanged: boolean,
51
+ isSaving: boolean,
52
+ },
53
+ container: {
54
+ settings: V1Model<{
55
+ post_title: string,
56
+ }>,
57
+ }
58
+ }
59
+
60
+ type V1Model<T> = {
61
+ get: <K extends keyof T = keyof T>( key: K ) => T[K],
62
+ }