@elementor/editor-site-navigation 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,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/components/top-bar/recently-edited.tsx
26
+ var React4 = __toESM(require("react"));
27
+ var import_ui4 = require("@elementor/ui");
28
+ var import_icons2 = require("@elementor/icons");
29
+ var import_editor_documents = require("@elementor/editor-documents");
30
+
31
+ // src/components/top-bar/indicator.tsx
32
+ var React = __toESM(require("react"));
33
+ var import_ui = require("@elementor/ui");
34
+ function Indicator({ title, status }) {
35
+ return /* @__PURE__ */ React.createElement(import_ui.Tooltip, { title }, /* @__PURE__ */ React.createElement(import_ui.Stack, { direction: "row", alignItems: "center", spacing: 2 }, /* @__PURE__ */ React.createElement(import_ui.Typography, { variant: "body2", sx: { maxWidth: "120px" }, noWrap: true }, title), status.value !== "publish" && /* @__PURE__ */ React.createElement(import_ui.Typography, { variant: "body2", sx: { fontStyle: "italic" } }, "(", status.label, ")")));
36
+ }
37
+
38
+ // src/components/top-bar/posts-list.tsx
39
+ var React3 = __toESM(require("react"));
40
+ var import_ui3 = require("@elementor/ui");
41
+ var import_i18n = require("@wordpress/i18n");
42
+
43
+ // src/components/top-bar/chip-doc-type.tsx
44
+ var import_icons = require("@elementor/icons");
45
+ var import_ui2 = require("@elementor/ui");
46
+ var React2 = __toESM(require("react"));
47
+ var iconsDocType = {
48
+ header: import_icons.HeaderTemplateIcon,
49
+ footer: import_icons.FooterTemplateIcon,
50
+ "single-post": import_icons.PostTypeIcon,
51
+ "single-page": import_icons.PageTypeIcon,
52
+ popup: import_icons.PopupTemplateIcon,
53
+ archive: import_icons.ArchiveTemplateIcon,
54
+ "search-results": import_icons.SearchResultsTemplateIcon,
55
+ "loop-item": import_icons.LoopItemTemplateIcon,
56
+ "error-404": import_icons.Error404TemplateIcon,
57
+ "landing-page": import_icons.LandingPageTemplateIcon,
58
+ page: import_icons.PageTemplateIcon,
59
+ section: import_icons.SectionTemplateIcon,
60
+ container: import_icons.ContainerTemplateIcon,
61
+ "wp-page": import_icons.PageTypeIcon,
62
+ "wp-post": import_icons.PostTypeIcon
63
+ };
64
+ function DocTypeChip({ postType, docType, label }) {
65
+ const color = "elementor_library" === postType ? "global" : "primary";
66
+ const Icon = iconsDocType?.[docType] || import_icons.PostTypeIcon;
67
+ return /* @__PURE__ */ React2.createElement(
68
+ import_ui2.Chip,
69
+ {
70
+ size: "medium",
71
+ variant: "standard",
72
+ label,
73
+ color,
74
+ icon: /* @__PURE__ */ React2.createElement(Icon, null)
75
+ }
76
+ );
77
+ }
78
+
79
+ // src/components/top-bar/posts-list.tsx
80
+ function PostsList({ recentPosts }) {
81
+ return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(import_ui3.ListSubheader, { sx: { fontSize: 12, fontStyle: "italic", pl: 4 }, component: "div", id: "nested-list-subheader" }, (0, import_i18n.__)("Recent", "elementor")), recentPosts.length ? recentPosts.map(({ title, edit_url: editUrl, type, id }) => /* @__PURE__ */ React3.createElement(
82
+ import_ui3.MenuItem,
83
+ {
84
+ key: id,
85
+ component: "a",
86
+ href: editUrl
87
+ },
88
+ title,
89
+ /* @__PURE__ */ React3.createElement(DocTypeChip, { postType: type.post_type, docType: type.doc_type, label: type.label })
90
+ )) : /* @__PURE__ */ React3.createElement(import_ui3.Typography, { variant: "caption", sx: { color: "grey.500", fontStyle: "italic", p: 4 }, component: "div", "aria-label": void 0 }, (0, import_i18n.__)("There are no other pages or templates on this site yet.", "elementor")));
91
+ }
92
+
93
+ // src/hooks/use-recent-posts.ts
94
+ var import_react = require("react");
95
+ var import_api_fetch = __toESM(require("@wordpress/api-fetch"));
96
+ var import_url = require("@wordpress/url");
97
+ function useRecentPosts(documentId) {
98
+ const [recentPosts, setRecentPosts] = (0, import_react.useState)([]);
99
+ const [isLoading, setIsLoading] = (0, import_react.useState)(false);
100
+ (0, import_react.useEffect)(() => {
101
+ if (documentId) {
102
+ setIsLoading(true);
103
+ fetchRecentlyEditedPosts(documentId).then((posts) => {
104
+ setRecentPosts(posts);
105
+ setIsLoading(false);
106
+ });
107
+ }
108
+ }, [documentId]);
109
+ return {
110
+ isLoading,
111
+ recentPosts
112
+ };
113
+ }
114
+ async function fetchRecentlyEditedPosts(documentId) {
115
+ const queryParams = {
116
+ posts_per_page: 5,
117
+ post__not_in: documentId
118
+ };
119
+ return await (0, import_api_fetch.default)({
120
+ path: (0, import_url.addQueryArgs)("/elementor/v1/site-navigation/recent-posts", queryParams)
121
+ }).then((response) => response).catch(() => []);
122
+ }
123
+
124
+ // src/components/top-bar/recently-edited.tsx
125
+ function RecentlyEdited() {
126
+ const activeDocument = (0, import_editor_documents.useActiveDocument)();
127
+ const hostDocument = (0, import_editor_documents.useHostDocument)();
128
+ const document = activeDocument && activeDocument.type.value !== "kit" ? activeDocument : hostDocument;
129
+ const { recentPosts } = useRecentPosts(document?.id);
130
+ const popupState = (0, import_ui4.usePopupState)({
131
+ variant: "popover",
132
+ popupId: "elementor-v2-top-bar-recently-edited"
133
+ });
134
+ if (!document) {
135
+ return null;
136
+ }
137
+ return /* @__PURE__ */ React4.createElement(import_ui4.Box, { sx: { cursor: "default" } }, /* @__PURE__ */ React4.createElement(
138
+ import_ui4.Button,
139
+ {
140
+ color: "inherit",
141
+ endIcon: /* @__PURE__ */ React4.createElement(import_icons2.ChevronDownIcon, null),
142
+ ...(0, import_ui4.bindTrigger)(popupState)
143
+ },
144
+ /* @__PURE__ */ React4.createElement(
145
+ Indicator,
146
+ {
147
+ title: document.title,
148
+ status: document.status
149
+ }
150
+ )
151
+ ), /* @__PURE__ */ React4.createElement(
152
+ import_ui4.Menu,
153
+ {
154
+ PaperProps: { sx: { minWidth: 314 } },
155
+ ...(0, import_ui4.bindMenu)(popupState)
156
+ },
157
+ /* @__PURE__ */ React4.createElement(PostsList, { recentPosts })
158
+ ));
159
+ }
160
+
161
+ // src/init.ts
162
+ var import_editor_app_bar = require("@elementor/editor-app-bar");
163
+ function init() {
164
+ registerTopBarMenuItems();
165
+ }
166
+ function registerTopBarMenuItems() {
167
+ (0, import_editor_app_bar.injectIntoPageIndication)({
168
+ name: "document-recently-edited",
169
+ filler: RecentlyEdited
170
+ });
171
+ }
172
+
173
+ // src/index.ts
174
+ init();
package/dist/index.mjs ADDED
@@ -0,0 +1,175 @@
1
+ // src/components/top-bar/recently-edited.tsx
2
+ import * as React4 from "react";
3
+ import {
4
+ bindMenu,
5
+ usePopupState,
6
+ bindTrigger,
7
+ Menu,
8
+ Button,
9
+ Box
10
+ } from "@elementor/ui";
11
+ import { ChevronDownIcon } from "@elementor/icons";
12
+ import { useActiveDocument, useHostDocument } from "@elementor/editor-documents";
13
+
14
+ // src/components/top-bar/indicator.tsx
15
+ import * as React from "react";
16
+ import { Tooltip, Typography, Stack } from "@elementor/ui";
17
+ function Indicator({ title, status }) {
18
+ return /* @__PURE__ */ React.createElement(Tooltip, { title }, /* @__PURE__ */ React.createElement(Stack, { direction: "row", alignItems: "center", spacing: 2 }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", sx: { maxWidth: "120px" }, noWrap: true }, title), status.value !== "publish" && /* @__PURE__ */ React.createElement(Typography, { variant: "body2", sx: { fontStyle: "italic" } }, "(", status.label, ")")));
19
+ }
20
+
21
+ // src/components/top-bar/posts-list.tsx
22
+ import * as React3 from "react";
23
+ import {
24
+ MenuItem,
25
+ ListSubheader,
26
+ Typography as Typography2
27
+ } from "@elementor/ui";
28
+ import { __ } from "@wordpress/i18n";
29
+
30
+ // src/components/top-bar/chip-doc-type.tsx
31
+ import {
32
+ ArchiveTemplateIcon,
33
+ HeaderTemplateIcon,
34
+ FooterTemplateIcon,
35
+ PostTypeIcon,
36
+ PageTypeIcon,
37
+ PopupTemplateIcon,
38
+ SearchResultsTemplateIcon,
39
+ Error404TemplateIcon,
40
+ LoopItemTemplateIcon,
41
+ LandingPageTemplateIcon,
42
+ PageTemplateIcon,
43
+ SectionTemplateIcon,
44
+ ContainerTemplateIcon
45
+ } from "@elementor/icons";
46
+ import { Chip } from "@elementor/ui";
47
+ import * as React2 from "react";
48
+ var iconsDocType = {
49
+ header: HeaderTemplateIcon,
50
+ footer: FooterTemplateIcon,
51
+ "single-post": PostTypeIcon,
52
+ "single-page": PageTypeIcon,
53
+ popup: PopupTemplateIcon,
54
+ archive: ArchiveTemplateIcon,
55
+ "search-results": SearchResultsTemplateIcon,
56
+ "loop-item": LoopItemTemplateIcon,
57
+ "error-404": Error404TemplateIcon,
58
+ "landing-page": LandingPageTemplateIcon,
59
+ page: PageTemplateIcon,
60
+ section: SectionTemplateIcon,
61
+ container: ContainerTemplateIcon,
62
+ "wp-page": PageTypeIcon,
63
+ "wp-post": PostTypeIcon
64
+ };
65
+ function DocTypeChip({ postType, docType, label }) {
66
+ const color = "elementor_library" === postType ? "global" : "primary";
67
+ const Icon = iconsDocType?.[docType] || PostTypeIcon;
68
+ return /* @__PURE__ */ React2.createElement(
69
+ Chip,
70
+ {
71
+ size: "medium",
72
+ variant: "standard",
73
+ label,
74
+ color,
75
+ icon: /* @__PURE__ */ React2.createElement(Icon, null)
76
+ }
77
+ );
78
+ }
79
+
80
+ // src/components/top-bar/posts-list.tsx
81
+ function PostsList({ recentPosts }) {
82
+ return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(ListSubheader, { sx: { fontSize: 12, fontStyle: "italic", pl: 4 }, component: "div", id: "nested-list-subheader" }, __("Recent", "elementor")), recentPosts.length ? recentPosts.map(({ title, edit_url: editUrl, type, id }) => /* @__PURE__ */ React3.createElement(
83
+ MenuItem,
84
+ {
85
+ key: id,
86
+ component: "a",
87
+ href: editUrl
88
+ },
89
+ title,
90
+ /* @__PURE__ */ React3.createElement(DocTypeChip, { postType: type.post_type, docType: type.doc_type, label: type.label })
91
+ )) : /* @__PURE__ */ React3.createElement(Typography2, { variant: "caption", sx: { color: "grey.500", fontStyle: "italic", p: 4 }, component: "div", "aria-label": void 0 }, __("There are no other pages or templates on this site yet.", "elementor")));
92
+ }
93
+
94
+ // src/hooks/use-recent-posts.ts
95
+ import { useEffect, useState } from "react";
96
+ import apiFetch from "@wordpress/api-fetch";
97
+ import { addQueryArgs } from "@wordpress/url";
98
+ function useRecentPosts(documentId) {
99
+ const [recentPosts, setRecentPosts] = useState([]);
100
+ const [isLoading, setIsLoading] = useState(false);
101
+ useEffect(() => {
102
+ if (documentId) {
103
+ setIsLoading(true);
104
+ fetchRecentlyEditedPosts(documentId).then((posts) => {
105
+ setRecentPosts(posts);
106
+ setIsLoading(false);
107
+ });
108
+ }
109
+ }, [documentId]);
110
+ return {
111
+ isLoading,
112
+ recentPosts
113
+ };
114
+ }
115
+ async function fetchRecentlyEditedPosts(documentId) {
116
+ const queryParams = {
117
+ posts_per_page: 5,
118
+ post__not_in: documentId
119
+ };
120
+ return await apiFetch({
121
+ path: addQueryArgs("/elementor/v1/site-navigation/recent-posts", queryParams)
122
+ }).then((response) => response).catch(() => []);
123
+ }
124
+
125
+ // src/components/top-bar/recently-edited.tsx
126
+ function RecentlyEdited() {
127
+ const activeDocument = useActiveDocument();
128
+ const hostDocument = useHostDocument();
129
+ const document = activeDocument && activeDocument.type.value !== "kit" ? activeDocument : hostDocument;
130
+ const { recentPosts } = useRecentPosts(document?.id);
131
+ const popupState = usePopupState({
132
+ variant: "popover",
133
+ popupId: "elementor-v2-top-bar-recently-edited"
134
+ });
135
+ if (!document) {
136
+ return null;
137
+ }
138
+ return /* @__PURE__ */ React4.createElement(Box, { sx: { cursor: "default" } }, /* @__PURE__ */ React4.createElement(
139
+ Button,
140
+ {
141
+ color: "inherit",
142
+ endIcon: /* @__PURE__ */ React4.createElement(ChevronDownIcon, null),
143
+ ...bindTrigger(popupState)
144
+ },
145
+ /* @__PURE__ */ React4.createElement(
146
+ Indicator,
147
+ {
148
+ title: document.title,
149
+ status: document.status
150
+ }
151
+ )
152
+ ), /* @__PURE__ */ React4.createElement(
153
+ Menu,
154
+ {
155
+ PaperProps: { sx: { minWidth: 314 } },
156
+ ...bindMenu(popupState)
157
+ },
158
+ /* @__PURE__ */ React4.createElement(PostsList, { recentPosts })
159
+ ));
160
+ }
161
+
162
+ // src/init.ts
163
+ import { injectIntoPageIndication } from "@elementor/editor-app-bar";
164
+ function init() {
165
+ registerTopBarMenuItems();
166
+ }
167
+ function registerTopBarMenuItems() {
168
+ injectIntoPageIndication({
169
+ name: "document-recently-edited",
170
+ filler: RecentlyEdited
171
+ });
172
+ }
173
+
174
+ // src/index.ts
175
+ init();
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@elementor/editor-site-navigation",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "author": "Elementor Team",
6
+ "homepage": "https://elementor.com/",
7
+ "license": "GPL-3.0-or-later",
8
+ "main": "dist/index.js",
9
+ "module": "dist/index.mjs",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/elementor/elementor-packages.git",
22
+ "directory": "packages/editor-site-navigation"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/elementor/elementor-packages/issues"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "scripts": {
31
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
32
+ "dev": "tsup src/index.ts --format esm --clean"
33
+ },
34
+ "dependencies": {
35
+ "@elementor/editor-app-bar": "^0.1.0",
36
+ "@elementor/editor-documents": "^0.1.0",
37
+ "@elementor/icons": "^0.1.0",
38
+ "@elementor/ui": "^1.4.47",
39
+ "@wordpress/api-fetch": "^6.27.0",
40
+ "@wordpress/i18n": "^4.31.0",
41
+ "@wordpress/url": "^3.31.0"
42
+ },
43
+ "peerDependencies": {
44
+ "react": "17.x"
45
+ },
46
+ "elementor": {
47
+ "type": "extension"
48
+ },
49
+ "gitHead": "2ba9f13a9dbd085eb6ed8e6e303e9275ce626b8d"
50
+ }
@@ -0,0 +1,180 @@
1
+ import * as React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { useHostDocument, useActiveDocument } from '@elementor/editor-documents';
4
+ import RecentlyEdited from '../recently-edited';
5
+ import { createMockDocument } from 'test-utils';
6
+ import useRecentPosts, { Post } from '../../../hooks/use-recent-posts';
7
+
8
+ jest.mock( '@elementor/editor-documents', () => ( {
9
+ useActiveDocument: jest.fn(),
10
+ useHostDocument: jest.fn(),
11
+ } ) );
12
+
13
+ jest.mock( '../../../hooks/use-recent-posts', () => (
14
+ {
15
+ default: jest.fn( () => ( { isLoading: false, recentPosts: [] } ) ),
16
+ __esModule: true,
17
+ }
18
+ ) );
19
+
20
+ describe( '@elementor/editor-site-navigation - Top bar Recently Edited', () => {
21
+ beforeEach( () => {
22
+ jest.mocked( useActiveDocument ).mockImplementation( () =>
23
+ createMockDocument( { id: 1, title: 'Active Document' } )
24
+ );
25
+
26
+ jest.mocked( useHostDocument ).mockImplementation( () =>
27
+ createMockDocument( { id: 2, title: 'Host Document' } )
28
+ );
29
+ } );
30
+
31
+ it( 'should show the title of the active document without its status when the document is published', async () => {
32
+ // Act.
33
+ const { queryByText } = render( <RecentlyEdited /> );
34
+
35
+ // Assert.
36
+ expect( queryByText( 'Active Document' ) ).toBeTruthy();
37
+ expect( queryByText( '(publish)' ) ).not.toBeTruthy();
38
+ } );
39
+
40
+ it( 'should show the title of the active document with its status when the document is not published', async () => {
41
+ // Arrange.
42
+ jest.mocked( useActiveDocument ).mockImplementation( () =>
43
+ createMockDocument( {
44
+ id: 1,
45
+ title: 'Active Document',
46
+ status: {
47
+ value: 'draft',
48
+ label: 'Draft',
49
+ },
50
+ } )
51
+ );
52
+
53
+ // Act.
54
+ const { queryByText } = render( <RecentlyEdited /> );
55
+
56
+ // Assert.
57
+ expect( queryByText( 'Active Document' ) ).toBeTruthy();
58
+ expect( queryByText( '(Draft)' ) ).toBeTruthy();
59
+ } );
60
+
61
+ it( 'should show the title of the host document when there is no active document', () => {
62
+ // Arrange.
63
+ jest.mocked( useActiveDocument ).mockImplementation( () => null );
64
+
65
+ // Act.
66
+ const { queryByText } = render( <RecentlyEdited /> );
67
+
68
+ // Assert.
69
+ expect( queryByText( 'Host Document' ) ).toBeTruthy();
70
+ } );
71
+
72
+ it( 'should show the title of the host document when the active document is kit', () => {
73
+ // Arrange.
74
+
75
+ jest.mocked( useActiveDocument ).mockImplementation( () =>
76
+ createMockDocument( {
77
+ id: 1,
78
+ title: 'Active Document',
79
+ type: {
80
+ value: 'kit',
81
+ label: 'Kit',
82
+ },
83
+ } )
84
+ );
85
+
86
+ // Act.
87
+ const { queryByText } = render( <RecentlyEdited /> );
88
+
89
+ // Assert.
90
+ expect( queryByText( 'Host Document' ) ).toBeTruthy();
91
+ } );
92
+
93
+ it( 'should show nothing if there are no documents', () => {
94
+ // Arrange.
95
+ jest.mocked( useActiveDocument ).mockImplementation( () => null );
96
+ jest.mocked( useHostDocument ).mockImplementation( () => null );
97
+
98
+ // Act.
99
+ const { queryByText } = render( <RecentlyEdited /> );
100
+
101
+ // Assert.
102
+ expect( queryByText( 'Host Document' ) ).not.toBeTruthy();
103
+ expect( queryByText( 'Active Document' ) ).not.toBeTruthy();
104
+ } );
105
+
106
+ it( 'should show empty state', () => {
107
+ // Arrange.
108
+ jest.mocked( useActiveDocument ).mockImplementation( () =>
109
+ createMockDocument( {
110
+ id: 1,
111
+ title: 'Header',
112
+ type: {
113
+ value: 'header',
114
+ label: 'Header',
115
+ },
116
+ } )
117
+ );
118
+
119
+ const isLoading = false;
120
+ const recentPosts: Post[] = [];
121
+
122
+ jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, recentPosts } );
123
+
124
+ const { getByText, getAllByRole } = render( <RecentlyEdited /> );
125
+
126
+ // Act.
127
+ const buttons = getAllByRole( 'button' );
128
+ buttons[ 0 ].click(); // Opens the recently edited menu
129
+
130
+ // Assert.
131
+ const label = getByText( 'There are no other pages or templates on this site yet', { exact: false } );
132
+ expect( label ).toBeInTheDocument();
133
+ } );
134
+
135
+ it( 'should open the recently edited menu on click', () => {
136
+ // Arrange.
137
+ jest.mocked( useActiveDocument ).mockImplementation( () =>
138
+ createMockDocument( {
139
+ id: 1,
140
+ title: 'Header',
141
+ type: {
142
+ value: 'header',
143
+ label: 'Header',
144
+ },
145
+ } )
146
+ );
147
+
148
+ const isLoading = false;
149
+ const recentPosts: Post[] = [ {
150
+ id: 1,
151
+ title: 'Test post',
152
+ edit_url: 'some_url',
153
+ type: {
154
+ post_type: 'post',
155
+ doc_type: 'wp-post',
156
+ label: 'Post',
157
+ },
158
+ date_modified: 123,
159
+ } ];
160
+
161
+ jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, recentPosts } );
162
+
163
+ const { getByText, getByRole, getAllByRole } = render( <RecentlyEdited /> );
164
+
165
+ // Act.
166
+ const buttons = getAllByRole( 'button' );
167
+ buttons[ 0 ].click(); // Opens the recently edited menu
168
+
169
+ // Assert.
170
+ const menu = getByRole( 'menu' );
171
+ expect( menu ).toBeInTheDocument();
172
+
173
+ const label = getByText( 'Recent' );
174
+ expect( label ).toBeInTheDocument();
175
+
176
+ const menuItems = getAllByRole( 'menuitem' );
177
+ expect( menuItems ).toHaveLength( 1 );
178
+ expect( getByText( 'Test post' ) ).toBeInTheDocument();
179
+ } );
180
+ } );
@@ -0,0 +1,60 @@
1
+ import {
2
+ ArchiveTemplateIcon,
3
+ HeaderTemplateIcon,
4
+ FooterTemplateIcon,
5
+ PostTypeIcon,
6
+ PageTypeIcon,
7
+ PopupTemplateIcon,
8
+ SearchResultsTemplateIcon,
9
+ Error404TemplateIcon,
10
+ LoopItemTemplateIcon,
11
+ LandingPageTemplateIcon,
12
+ PageTemplateIcon,
13
+ SectionTemplateIcon,
14
+ ContainerTemplateIcon,
15
+ } from '@elementor/icons';
16
+ import { Chip } from '@elementor/ui';
17
+ import { DocType } from '../../types';
18
+ import * as React from 'react';
19
+
20
+ type DocTypes = {
21
+ [key in DocType]: React.ElementType;
22
+ };
23
+
24
+ const iconsDocType: DocTypes = {
25
+ header: HeaderTemplateIcon,
26
+ footer: FooterTemplateIcon,
27
+ 'single-post': PostTypeIcon,
28
+ 'single-page': PageTypeIcon,
29
+ popup: PopupTemplateIcon,
30
+ archive: ArchiveTemplateIcon,
31
+ 'search-results': SearchResultsTemplateIcon,
32
+ 'loop-item': LoopItemTemplateIcon,
33
+ 'error-404': Error404TemplateIcon,
34
+ 'landing-page': LandingPageTemplateIcon,
35
+ page: PageTemplateIcon,
36
+ section: SectionTemplateIcon,
37
+ container: ContainerTemplateIcon,
38
+ 'wp-page': PageTypeIcon,
39
+ 'wp-post': PostTypeIcon,
40
+ };
41
+
42
+ export type Props = {
43
+ postType: string;
44
+ docType: DocType;
45
+ label: string;
46
+ };
47
+ export default function DocTypeChip( { postType, docType, label }: Props ) {
48
+ const color = 'elementor_library' === postType ? 'global' : 'primary';
49
+ const Icon = iconsDocType?.[ docType ] || PostTypeIcon;
50
+
51
+ return (
52
+ <Chip
53
+ size="medium"
54
+ variant="standard"
55
+ label={ label }
56
+ color={ color }
57
+ icon={ <Icon /> }
58
+ />
59
+ );
60
+ }
@@ -0,0 +1,25 @@
1
+ import * as React from 'react';
2
+ import { Tooltip, Typography, Stack } from '@elementor/ui';
3
+ import { Document } from '@elementor/editor-documents';
4
+
5
+ type Props = {
6
+ title: Document['title'],
7
+ status: Document['status']
8
+ }
9
+
10
+ export default function Indicator( { title, status }: Props ) {
11
+ return (
12
+ <Tooltip title={ title }>
13
+ <Stack direction="row" alignItems="center" spacing={ 2 }>
14
+ <Typography variant="body2" sx={ { maxWidth: '120px' } } noWrap>
15
+ { title }
16
+ </Typography>
17
+ { status.value !== 'publish' &&
18
+ <Typography variant="body2" sx={ { fontStyle: 'italic' } }>
19
+ ({ status.label })
20
+ </Typography>
21
+ }
22
+ </Stack>
23
+ </Tooltip>
24
+ );
25
+ }