@elementor/editor-site-navigation 0.1.0 → 0.2.1

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## [0.2.1](https://github.com/elementor/elementor-packages/compare/v0.2.0...v0.2.1) (2023-05-11)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **editor-app-bar:** make save options button disabled in site-settings [ED-10221] ([#30](https://github.com/elementor/elementor-packages/issues/30)) ([1991096](https://github.com/elementor/elementor-packages/commit/1991096115efeae7bc3648e4889899b85d7328d6))
package/dist/index.d.ts CHANGED
@@ -1,2 +1,8 @@
1
+ import * as React from 'react';
1
2
 
2
- export { }
3
+ type IconsMap = {
4
+ [key: string]: React.ElementType;
5
+ };
6
+ declare function extendIconsMap(additionalIcons: IconsMap): void;
7
+
8
+ export { extendIconsMap };
package/dist/index.js CHANGED
@@ -5,6 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
8
12
  var __copyProps = (to, from, except, desc) => {
9
13
  if (from && typeof from === "object" || typeof from === "function") {
10
14
  for (let key of __getOwnPropNames(from))
@@ -21,49 +25,73 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
25
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
26
  mod
23
27
  ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ extendIconsMap: () => extendIconsMap
34
+ });
35
+ module.exports = __toCommonJS(src_exports);
36
+
37
+ // src/icons-map.ts
38
+ var import_icons = require("@elementor/icons");
39
+ var initialIconsMap = {
40
+ page: import_icons.PageTemplateIcon,
41
+ section: import_icons.SectionTemplateIcon,
42
+ container: import_icons.ContainerTemplateIcon,
43
+ "wp-page": import_icons.PageTypeIcon,
44
+ "wp-post": import_icons.PostTypeIcon
45
+ };
46
+ var iconsMap = { ...initialIconsMap };
47
+ function extendIconsMap(additionalIcons) {
48
+ Object.assign(iconsMap, additionalIcons);
49
+ }
50
+ function getIconsMap() {
51
+ return iconsMap;
52
+ }
24
53
 
25
54
  // src/components/top-bar/recently-edited.tsx
26
55
  var React4 = __toESM(require("react"));
27
56
  var import_ui4 = require("@elementor/ui");
28
- var import_icons2 = require("@elementor/icons");
57
+ var import_icons4 = require("@elementor/icons");
29
58
  var import_editor_documents = require("@elementor/editor-documents");
30
59
 
31
60
  // src/components/top-bar/indicator.tsx
32
61
  var React = __toESM(require("react"));
33
62
  var import_ui = require("@elementor/ui");
34
63
  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, ")")));
64
+ return /* @__PURE__ */ React.createElement(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, ")")));
65
+ }
66
+ function Tooltip(props) {
67
+ return /* @__PURE__ */ React.createElement(
68
+ import_ui.Tooltip,
69
+ {
70
+ PopperProps: {
71
+ sx: {
72
+ "&.MuiTooltip-popper .MuiTooltip-tooltip.MuiTooltip-tooltipPlacementBottom": {
73
+ mt: 7
74
+ }
75
+ }
76
+ },
77
+ ...props
78
+ }
79
+ );
36
80
  }
37
81
 
38
82
  // src/components/top-bar/posts-list.tsx
39
- var React3 = __toESM(require("react"));
40
83
  var import_ui3 = require("@elementor/ui");
84
+ var React3 = __toESM(require("react"));
41
85
  var import_i18n = require("@wordpress/i18n");
42
86
 
43
87
  // src/components/top-bar/chip-doc-type.tsx
44
- var import_icons = require("@elementor/icons");
88
+ var import_icons2 = require("@elementor/icons");
45
89
  var import_ui2 = require("@elementor/ui");
46
90
  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
- };
91
+ var iconsMap2 = getIconsMap();
64
92
  function DocTypeChip({ postType, docType, label }) {
65
93
  const color = "elementor_library" === postType ? "global" : "primary";
66
- const Icon = iconsDocType?.[docType] || import_icons.PostTypeIcon;
94
+ const Icon = iconsMap2[docType] || import_icons2.PostTypeIcon;
67
95
  return /* @__PURE__ */ React2.createElement(
68
96
  import_ui2.Chip,
69
97
  {
@@ -76,28 +104,73 @@ function DocTypeChip({ postType, docType, label }) {
76
104
  );
77
105
  }
78
106
 
107
+ // src/hooks/use-create-page.ts
108
+ var import_api_fetch = __toESM(require("@wordpress/api-fetch"));
109
+ var import_react = require("react");
110
+ var endpointPath = "/elementor/v1/site-navigation/add-new-post";
111
+ function useCreatePage({ onCreated: onCreated2 }) {
112
+ const [isLoading, setIsLoading] = (0, import_react.useState)(false);
113
+ return {
114
+ create: () => {
115
+ setIsLoading(true);
116
+ addNewPage().then((newPost) => newPost).then((newPost) => {
117
+ setIsLoading(false);
118
+ onCreated2(newPost.edit_url);
119
+ }).catch(() => {
120
+ setIsLoading(false);
121
+ });
122
+ },
123
+ isLoading
124
+ };
125
+ }
126
+ async function addNewPage() {
127
+ return await (0, import_api_fetch.default)({
128
+ path: endpointPath,
129
+ method: "POST",
130
+ data: { post_type: "page" }
131
+ });
132
+ }
133
+
79
134
  // src/components/top-bar/posts-list.tsx
135
+ var import_icons3 = require("@elementor/icons");
80
136
  function PostsList({ recentPosts }) {
137
+ const { create, isLoading } = useCreatePage({ onCreated });
81
138
  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
139
  import_ui3.MenuItem,
83
140
  {
141
+ dense: true,
84
142
  key: id,
85
143
  component: "a",
86
144
  href: editUrl
87
145
  },
88
146
  title,
89
147
  /* @__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")));
148
+ )) : /* @__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")), /* @__PURE__ */ React3.createElement(import_ui3.Divider, null), /* @__PURE__ */ React3.createElement(
149
+ import_ui3.MenuItem,
150
+ {
151
+ dense: true,
152
+ size: "small",
153
+ color: "inherit",
154
+ component: "div",
155
+ onClick: create
156
+ },
157
+ /* @__PURE__ */ React3.createElement(import_ui3.ListItemIcon, null, isLoading ? /* @__PURE__ */ React3.createElement(import_ui3.CircularProgress, null) : /* @__PURE__ */ React3.createElement(import_icons3.PlusIcon, null)),
158
+ (0, import_i18n.__)("Add new page", "elementor")
159
+ ));
160
+ }
161
+ function onCreated(url) {
162
+ window.location.href = url;
91
163
  }
92
164
 
93
165
  // src/hooks/use-recent-posts.ts
94
- var import_react = require("react");
95
- var import_api_fetch = __toESM(require("@wordpress/api-fetch"));
166
+ var import_react2 = require("react");
167
+ var import_api_fetch2 = __toESM(require("@wordpress/api-fetch"));
96
168
  var import_url = require("@wordpress/url");
169
+ var endpointPath2 = "/elementor/v1/site-navigation/recent-posts";
97
170
  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)(() => {
171
+ const [recentPosts, setRecentPosts] = (0, import_react2.useState)([]);
172
+ const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
173
+ (0, import_react2.useEffect)(() => {
101
174
  if (documentId) {
102
175
  setIsLoading(true);
103
176
  fetchRecentlyEditedPosts(documentId).then((posts) => {
@@ -116,8 +189,8 @@ async function fetchRecentlyEditedPosts(documentId) {
116
189
  posts_per_page: 5,
117
190
  post__not_in: documentId
118
191
  };
119
- return await (0, import_api_fetch.default)({
120
- path: (0, import_url.addQueryArgs)("/elementor/v1/site-navigation/recent-posts", queryParams)
192
+ return await (0, import_api_fetch2.default)({
193
+ path: (0, import_url.addQueryArgs)(endpointPath2, queryParams)
121
194
  }).then((response) => response).catch(() => []);
122
195
  }
123
196
 
@@ -138,7 +211,8 @@ function RecentlyEdited() {
138
211
  import_ui4.Button,
139
212
  {
140
213
  color: "inherit",
141
- endIcon: /* @__PURE__ */ React4.createElement(import_icons2.ChevronDownIcon, null),
214
+ size: "small",
215
+ endIcon: /* @__PURE__ */ React4.createElement(import_icons4.ChevronDownIcon, { fontSize: "small" }),
142
216
  ...(0, import_ui4.bindTrigger)(popupState)
143
217
  },
144
218
  /* @__PURE__ */ React4.createElement(
@@ -151,7 +225,8 @@ function RecentlyEdited() {
151
225
  ), /* @__PURE__ */ React4.createElement(
152
226
  import_ui4.Menu,
153
227
  {
154
- PaperProps: { sx: { minWidth: 314 } },
228
+ MenuListProps: { component: "div" },
229
+ PaperProps: { sx: { mt: 4, minWidth: 314 } },
155
230
  ...(0, import_ui4.bindMenu)(popupState)
156
231
  },
157
232
  /* @__PURE__ */ React4.createElement(PostsList, { recentPosts })
@@ -172,3 +247,7 @@ function registerTopBarMenuItems() {
172
247
 
173
248
  // src/index.ts
174
249
  init();
250
+ // Annotate the CommonJS export names for ESM import in node:
251
+ 0 && (module.exports = {
252
+ extendIconsMap
253
+ });
package/dist/index.mjs CHANGED
@@ -1,3 +1,26 @@
1
+ // src/icons-map.ts
2
+ import {
3
+ PostTypeIcon,
4
+ PageTypeIcon,
5
+ PageTemplateIcon,
6
+ SectionTemplateIcon,
7
+ ContainerTemplateIcon
8
+ } from "@elementor/icons";
9
+ var initialIconsMap = {
10
+ page: PageTemplateIcon,
11
+ section: SectionTemplateIcon,
12
+ container: ContainerTemplateIcon,
13
+ "wp-page": PageTypeIcon,
14
+ "wp-post": PostTypeIcon
15
+ };
16
+ var iconsMap = { ...initialIconsMap };
17
+ function extendIconsMap(additionalIcons) {
18
+ Object.assign(iconsMap, additionalIcons);
19
+ }
20
+ function getIconsMap() {
21
+ return iconsMap;
22
+ }
23
+
1
24
  // src/components/top-bar/recently-edited.tsx
2
25
  import * as React4 from "react";
3
26
  import {
@@ -13,58 +36,48 @@ import { useActiveDocument, useHostDocument } from "@elementor/editor-documents"
13
36
 
14
37
  // src/components/top-bar/indicator.tsx
15
38
  import * as React from "react";
16
- import { Tooltip, Typography, Stack } from "@elementor/ui";
39
+ import { Typography, Stack, Tooltip as BaseTooltip } from "@elementor/ui";
17
40
  function Indicator({ title, status }) {
18
41
  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
42
  }
43
+ function Tooltip(props) {
44
+ return /* @__PURE__ */ React.createElement(
45
+ BaseTooltip,
46
+ {
47
+ PopperProps: {
48
+ sx: {
49
+ "&.MuiTooltip-popper .MuiTooltip-tooltip.MuiTooltip-tooltipPlacementBottom": {
50
+ mt: 7
51
+ }
52
+ }
53
+ },
54
+ ...props
55
+ }
56
+ );
57
+ }
20
58
 
21
59
  // src/components/top-bar/posts-list.tsx
22
- import * as React3 from "react";
23
60
  import {
61
+ Divider,
24
62
  MenuItem,
63
+ ListItemIcon,
25
64
  ListSubheader,
26
- Typography as Typography2
65
+ Typography as Typography2,
66
+ CircularProgress
27
67
  } from "@elementor/ui";
68
+ import * as React3 from "react";
28
69
  import { __ } from "@wordpress/i18n";
29
70
 
30
71
  // src/components/top-bar/chip-doc-type.tsx
31
72
  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
73
+ PostTypeIcon as PostTypeIcon2
45
74
  } from "@elementor/icons";
46
75
  import { Chip } from "@elementor/ui";
47
76
  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
- };
77
+ var iconsMap2 = getIconsMap();
65
78
  function DocTypeChip({ postType, docType, label }) {
66
79
  const color = "elementor_library" === postType ? "global" : "primary";
67
- const Icon = iconsDocType?.[docType] || PostTypeIcon;
80
+ const Icon = iconsMap2[docType] || PostTypeIcon2;
68
81
  return /* @__PURE__ */ React2.createElement(
69
82
  Chip,
70
83
  {
@@ -77,27 +90,72 @@ function DocTypeChip({ postType, docType, label }) {
77
90
  );
78
91
  }
79
92
 
93
+ // src/hooks/use-create-page.ts
94
+ import apiFetch from "@wordpress/api-fetch";
95
+ import { useState } from "react";
96
+ var endpointPath = "/elementor/v1/site-navigation/add-new-post";
97
+ function useCreatePage({ onCreated: onCreated2 }) {
98
+ const [isLoading, setIsLoading] = useState(false);
99
+ return {
100
+ create: () => {
101
+ setIsLoading(true);
102
+ addNewPage().then((newPost) => newPost).then((newPost) => {
103
+ setIsLoading(false);
104
+ onCreated2(newPost.edit_url);
105
+ }).catch(() => {
106
+ setIsLoading(false);
107
+ });
108
+ },
109
+ isLoading
110
+ };
111
+ }
112
+ async function addNewPage() {
113
+ return await apiFetch({
114
+ path: endpointPath,
115
+ method: "POST",
116
+ data: { post_type: "page" }
117
+ });
118
+ }
119
+
80
120
  // src/components/top-bar/posts-list.tsx
121
+ import { PlusIcon } from "@elementor/icons";
81
122
  function PostsList({ recentPosts }) {
123
+ const { create, isLoading } = useCreatePage({ onCreated });
82
124
  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
125
  MenuItem,
84
126
  {
127
+ dense: true,
85
128
  key: id,
86
129
  component: "a",
87
130
  href: editUrl
88
131
  },
89
132
  title,
90
133
  /* @__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")));
134
+ )) : /* @__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")), /* @__PURE__ */ React3.createElement(Divider, null), /* @__PURE__ */ React3.createElement(
135
+ MenuItem,
136
+ {
137
+ dense: true,
138
+ size: "small",
139
+ color: "inherit",
140
+ component: "div",
141
+ onClick: create
142
+ },
143
+ /* @__PURE__ */ React3.createElement(ListItemIcon, null, isLoading ? /* @__PURE__ */ React3.createElement(CircularProgress, null) : /* @__PURE__ */ React3.createElement(PlusIcon, null)),
144
+ __("Add new page", "elementor")
145
+ ));
146
+ }
147
+ function onCreated(url) {
148
+ window.location.href = url;
92
149
  }
93
150
 
94
151
  // src/hooks/use-recent-posts.ts
95
- import { useEffect, useState } from "react";
96
- import apiFetch from "@wordpress/api-fetch";
152
+ import { useEffect, useState as useState2 } from "react";
153
+ import apiFetch2 from "@wordpress/api-fetch";
97
154
  import { addQueryArgs } from "@wordpress/url";
155
+ var endpointPath2 = "/elementor/v1/site-navigation/recent-posts";
98
156
  function useRecentPosts(documentId) {
99
- const [recentPosts, setRecentPosts] = useState([]);
100
- const [isLoading, setIsLoading] = useState(false);
157
+ const [recentPosts, setRecentPosts] = useState2([]);
158
+ const [isLoading, setIsLoading] = useState2(false);
101
159
  useEffect(() => {
102
160
  if (documentId) {
103
161
  setIsLoading(true);
@@ -117,8 +175,8 @@ async function fetchRecentlyEditedPosts(documentId) {
117
175
  posts_per_page: 5,
118
176
  post__not_in: documentId
119
177
  };
120
- return await apiFetch({
121
- path: addQueryArgs("/elementor/v1/site-navigation/recent-posts", queryParams)
178
+ return await apiFetch2({
179
+ path: addQueryArgs(endpointPath2, queryParams)
122
180
  }).then((response) => response).catch(() => []);
123
181
  }
124
182
 
@@ -139,7 +197,8 @@ function RecentlyEdited() {
139
197
  Button,
140
198
  {
141
199
  color: "inherit",
142
- endIcon: /* @__PURE__ */ React4.createElement(ChevronDownIcon, null),
200
+ size: "small",
201
+ endIcon: /* @__PURE__ */ React4.createElement(ChevronDownIcon, { fontSize: "small" }),
143
202
  ...bindTrigger(popupState)
144
203
  },
145
204
  /* @__PURE__ */ React4.createElement(
@@ -152,7 +211,8 @@ function RecentlyEdited() {
152
211
  ), /* @__PURE__ */ React4.createElement(
153
212
  Menu,
154
213
  {
155
- PaperProps: { sx: { minWidth: 314 } },
214
+ MenuListProps: { component: "div" },
215
+ PaperProps: { sx: { mt: 4, minWidth: 314 } },
156
216
  ...bindMenu(popupState)
157
217
  },
158
218
  /* @__PURE__ */ React4.createElement(PostsList, { recentPosts })
@@ -173,3 +233,6 @@ function registerTopBarMenuItems() {
173
233
 
174
234
  // src/index.ts
175
235
  init();
236
+ export {
237
+ extendIconsMap
238
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-site-navigation",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -19,7 +19,7 @@
19
19
  "repository": {
20
20
  "type": "git",
21
21
  "url": "https://github.com/elementor/elementor-packages.git",
22
- "directory": "packages/editor-site-navigation"
22
+ "directory": "packages/core/editor-site-navigation"
23
23
  },
24
24
  "bugs": {
25
25
  "url": "https://github.com/elementor/elementor-packages/issues"
@@ -32,10 +32,10 @@
32
32
  "dev": "tsup src/index.ts --format esm --clean"
33
33
  },
34
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",
35
+ "@elementor/editor-app-bar": "^0.2.1",
36
+ "@elementor/editor-documents": "^0.2.0",
37
+ "@elementor/icons": "^0.2.1",
38
+ "@elementor/ui": "^1.4.50",
39
39
  "@wordpress/api-fetch": "^6.27.0",
40
40
  "@wordpress/i18n": "^4.31.0",
41
41
  "@wordpress/url": "^3.31.0"
@@ -46,5 +46,5 @@
46
46
  "elementor": {
47
47
  "type": "extension"
48
48
  },
49
- "gitHead": "2ba9f13a9dbd085eb6ed8e6e303e9275ce626b8d"
49
+ "gitHead": "3559bb26178072f23c08caab4114671a76bb9eb6"
50
50
  }
@@ -0,0 +1,33 @@
1
+ import * as React from 'react';
2
+ import { getIconsMap, extendIconsMap, resetIconsMap } from '../icons-map';
3
+
4
+ describe( '@elementor/site-navigation - iconsMap', () => {
5
+ afterEach( () => {
6
+ resetIconsMap();
7
+ } );
8
+
9
+ it( 'should override existing icons', async () => {
10
+ // Arrange.
11
+ const initialIcons = getIconsMap();
12
+ const addedIcons = {
13
+ 'added-icon': () => <div>added-icon</div>,
14
+ };
15
+
16
+ // Act - add icons.
17
+ extendIconsMap( addedIcons );
18
+
19
+ // Assert - should be initial and added.
20
+ expect( getIconsMap() ).toEqual( { ...initialIcons, ...addedIcons } );
21
+
22
+ // Arrange.
23
+ const overrideIcons = {
24
+ 'added-icon': () => <div>override-icon</div>,
25
+ };
26
+
27
+ // Act - override exits icons.
28
+ extendIconsMap( overrideIcons );
29
+
30
+ // Assert - should override exits icons.
31
+ expect( getIconsMap() ).toEqual( { ...initialIcons, ...overrideIcons } );
32
+ } );
33
+ } );
@@ -0,0 +1,94 @@
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
+ import useCreatePage from '../../../hooks/use-create-page';
8
+
9
+ jest.mock( '@elementor/editor-documents', () => ( {
10
+ useActiveDocument: jest.fn(),
11
+ useHostDocument: jest.fn(),
12
+ } ) );
13
+
14
+ jest.mock( '../../../hooks/use-recent-posts', () => (
15
+ {
16
+ default: jest.fn( () => ( { isLoading: false, recentPosts: [] } ) ),
17
+ __esModule: true,
18
+ }
19
+ ) );
20
+ jest.mock( '../../../hooks/use-create-page', () => (
21
+ {
22
+ default: jest.fn( () => ( { create: jest.fn(), isLoading: false } ) ),
23
+ __esModule: true,
24
+ }
25
+ ) );
26
+
27
+ describe( '@elementor/recently-edited - Top bar add new page', () => {
28
+ beforeEach( () => {
29
+ jest.mocked( useActiveDocument ).mockImplementation( () =>
30
+ createMockDocument( { id: 1, title: 'Active Document' } )
31
+ );
32
+
33
+ jest.mocked( useHostDocument ).mockImplementation( () =>
34
+ createMockDocument( { id: 2, title: 'Host Document' } )
35
+ );
36
+ } );
37
+
38
+ it( 'should render add new page button', () => {
39
+ // Arrange.
40
+ mockActiveDocument();
41
+
42
+ const isLoading = false;
43
+ const recentPosts: Post[] = [];
44
+
45
+ jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, recentPosts } );
46
+
47
+ const { getByText, getAllByRole } = render( <RecentlyEdited /> );
48
+
49
+ // Act.
50
+ const buttons = getAllByRole( 'button' );
51
+ buttons[ 0 ].click(); // Opens the recently edited menu
52
+
53
+ // Assert.
54
+ const label = getByText( 'Add new page', { exact: false } );
55
+ expect( label ).toBeInTheDocument();
56
+ } );
57
+
58
+ it( 'should trigger create page hook on click', () => {
59
+ // Arrange.
60
+ mockActiveDocument();
61
+
62
+ const isLoading = false;
63
+ const recentPosts: Post[] = [];
64
+ const create = jest.fn();
65
+
66
+ jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, recentPosts } );
67
+ jest.mocked( useCreatePage ).mockReturnValue( { isLoading, create } );
68
+
69
+ const { getByText, getAllByRole } = render( <RecentlyEdited /> );
70
+
71
+ // Act.
72
+ const buttons = getAllByRole( 'button' );
73
+ buttons[ 0 ].click(); // Opens the recently edited menu
74
+
75
+ const addNewPage = getByText( 'Add new page', { exact: false } );
76
+ addNewPage.click();
77
+
78
+ // Assert.
79
+ expect( create ).toHaveBeenCalledTimes( 1 );
80
+ } );
81
+ } );
82
+
83
+ function mockActiveDocument() {
84
+ jest.mocked( useActiveDocument ).mockImplementation( () =>
85
+ createMockDocument( {
86
+ id: 1,
87
+ title: 'Header',
88
+ type: {
89
+ value: 'header',
90
+ label: 'Header',
91
+ },
92
+ } )
93
+ );
94
+ }
@@ -17,7 +17,7 @@ jest.mock( '../../../hooks/use-recent-posts', () => (
17
17
  }
18
18
  ) );
19
19
 
20
- describe( '@elementor/editor-site-navigation - Top bar Recently Edited', () => {
20
+ describe( '@elementor/recently-edited - Top bar Recently Edited', () => {
21
21
  beforeEach( () => {
22
22
  jest.mocked( useActiveDocument ).mockImplementation( () =>
23
23
  createMockDocument( { id: 1, title: 'Active Document' } )
@@ -173,8 +173,6 @@ describe( '@elementor/editor-site-navigation - Top bar Recently Edited', () => {
173
173
  const label = getByText( 'Recent' );
174
174
  expect( label ).toBeInTheDocument();
175
175
 
176
- const menuItems = getAllByRole( 'menuitem' );
177
- expect( menuItems ).toHaveLength( 1 );
178
176
  expect( getByText( 'Test post' ) ).toBeInTheDocument();
179
177
  } );
180
178
  } );
@@ -1,52 +1,21 @@
1
1
  import {
2
- ArchiveTemplateIcon,
3
- HeaderTemplateIcon,
4
- FooterTemplateIcon,
5
2
  PostTypeIcon,
6
- PageTypeIcon,
7
- PopupTemplateIcon,
8
- SearchResultsTemplateIcon,
9
- Error404TemplateIcon,
10
- LoopItemTemplateIcon,
11
- LandingPageTemplateIcon,
12
- PageTemplateIcon,
13
- SectionTemplateIcon,
14
- ContainerTemplateIcon,
15
3
  } from '@elementor/icons';
16
4
  import { Chip } from '@elementor/ui';
17
- import { DocType } from '../../types';
18
5
  import * as React from 'react';
6
+ import { getIconsMap } from '../../icons-map';
19
7
 
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
- };
8
+ const iconsMap = getIconsMap();
41
9
 
42
10
  export type Props = {
43
11
  postType: string;
44
- docType: DocType;
12
+ docType: string;
45
13
  label: string;
46
14
  };
15
+
47
16
  export default function DocTypeChip( { postType, docType, label }: Props ) {
48
17
  const color = 'elementor_library' === postType ? 'global' : 'primary';
49
- const Icon = iconsDocType?.[ docType ] || PostTypeIcon;
18
+ const Icon = iconsMap[ docType ] || PostTypeIcon;
50
19
 
51
20
  return (
52
21
  <Chip
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { Tooltip, Typography, Stack } from '@elementor/ui';
2
+ import { Typography, Stack, Tooltip as BaseTooltip, TooltipProps } from '@elementor/ui';
3
3
  import { Document } from '@elementor/editor-documents';
4
4
 
5
5
  type Props = {
@@ -23,3 +23,16 @@ export default function Indicator( { title, status }: Props ) {
23
23
  </Tooltip>
24
24
  );
25
25
  }
26
+
27
+ function Tooltip( props: TooltipProps ) {
28
+ return <BaseTooltip
29
+ PopperProps={ {
30
+ sx: {
31
+ '&.MuiTooltip-popper .MuiTooltip-tooltip.MuiTooltip-tooltipPlacementBottom': {
32
+ mt: 7,
33
+ },
34
+ },
35
+ } }
36
+ { ...props }
37
+ />;
38
+ }
@@ -1,19 +1,26 @@
1
- import * as React from 'react';
2
1
  import {
2
+ Divider,
3
3
  MenuItem,
4
+ ListItemIcon,
4
5
  ListSubheader,
5
6
  Typography,
7
+ CircularProgress,
6
8
  } from '@elementor/ui';
7
9
 
10
+ import * as React from 'react';
8
11
  import { __ } from '@wordpress/i18n';
9
- import DocTypeChip, { Props } from '../top-bar/chip-doc-type';
12
+ import DocTypeChip, { Props } from './chip-doc-type';
10
13
  import { Post } from '../../hooks/use-recent-posts';
14
+ import useCreatePage from '../../hooks/use-create-page';
15
+ import { PlusIcon } from '@elementor/icons';
11
16
 
12
17
  export type RecentPostsProps = {
13
18
  recentPosts: Post[];
14
19
  };
15
20
 
16
21
  export default function PostsList( { recentPosts }: RecentPostsProps ) {
22
+ const { create, isLoading } = useCreatePage( { onCreated } );
23
+
17
24
  return (
18
25
  <>
19
26
  <ListSubheader sx={ { fontSize: 12, fontStyle: 'italic', pl: 4 } } component="div" id="nested-list-subheader">
@@ -23,6 +30,7 @@ export default function PostsList( { recentPosts }: RecentPostsProps ) {
23
30
  { recentPosts.length
24
31
  ? ( recentPosts.map( ( { title, edit_url: editUrl, type, id } ) => (
25
32
  <MenuItem
33
+ dense
26
34
  key={ id }
27
35
  component="a"
28
36
  href={ editUrl }
@@ -36,6 +44,24 @@ export default function PostsList( { recentPosts }: RecentPostsProps ) {
36
44
  </Typography>
37
45
  )
38
46
  }
47
+ <Divider />
48
+ <MenuItem
49
+ dense
50
+ size="small"
51
+ color="inherit"
52
+ component="div"
53
+ onClick={ create }
54
+ >
55
+ <ListItemIcon>
56
+ { isLoading ? <CircularProgress /> : <PlusIcon /> }
57
+ </ListItemIcon>
58
+
59
+ { __( 'Add new page', 'elementor' ) }
60
+ </MenuItem>
39
61
  </>
40
62
  );
41
63
  }
64
+
65
+ function onCreated( url: string ) {
66
+ window.location.href = url;
67
+ }
@@ -35,7 +35,8 @@ export default function RecentlyEdited() {
35
35
  <Box sx={ { cursor: 'default' } }>
36
36
  <Button
37
37
  color="inherit"
38
- endIcon={ <ChevronDownIcon /> }
38
+ size="small"
39
+ endIcon={ <ChevronDownIcon fontSize="small" /> }
39
40
  { ...bindTrigger( popupState ) }
40
41
  >
41
42
  <Indicator
@@ -45,7 +46,8 @@ export default function RecentlyEdited() {
45
46
  </Button>
46
47
 
47
48
  <Menu
48
- PaperProps={ { sx: { minWidth: 314 } } }
49
+ MenuListProps={ { component: 'div' } }
50
+ PaperProps={ { sx: { mt: 4, minWidth: 314 } } }
49
51
  { ...bindMenu( popupState ) }
50
52
  >
51
53
  <PostsList recentPosts={ recentPosts } />
@@ -0,0 +1,43 @@
1
+ import { renderHook } from '@testing-library/react-hooks';
2
+ import { waitFor } from '@testing-library/react';
3
+ import apiFetch from '@wordpress/api-fetch';
4
+ import useCreatePage, { endpointPath } from '../use-create-page';
5
+
6
+ // Mock apiFetch to return a promise that resolves to an empty array.
7
+ jest.mock( '@wordpress/api-fetch' );
8
+
9
+ describe( '@elementor/recently-edited/use-page', () => {
10
+ beforeEach( () => {
11
+ jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( [] ) );
12
+ } );
13
+
14
+ afterEach( () => {
15
+ jest.clearAllMocks();
16
+ } );
17
+
18
+ it( 'should run useCreatePage hook', async () => {
19
+ // Arrange.
20
+ const onCreated = jest.fn();
21
+ const { result } = renderHook( () => useCreatePage( { onCreated } ) );
22
+ const newPost = {
23
+ id: 1,
24
+ edit_url: 'editurl.com',
25
+ };
26
+ jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( newPost ) );
27
+
28
+ const { create } = result.current;
29
+
30
+ // Act.
31
+ create();
32
+
33
+ // Assert.
34
+ await waitFor( () => {
35
+ expect( apiFetch ).toHaveBeenCalledWith( {
36
+ data: { post_type: 'page' },
37
+ method: 'POST',
38
+ path: endpointPath,
39
+ } );
40
+ expect( apiFetch ).toHaveBeenCalledTimes( 1 );
41
+ } );
42
+ } );
43
+ } );
@@ -0,0 +1,42 @@
1
+ import { waitFor } from '@testing-library/react';
2
+ import { renderHook } from '@testing-library/react-hooks';
3
+ import apiFetch from '@wordpress/api-fetch';
4
+ import useRecentPosts, { endpointPath } from '../use-recent-posts';
5
+
6
+ // Mock apiFetch to return a promise that resolves to an empty array.
7
+ jest.mock( '@wordpress/api-fetch' );
8
+
9
+ describe( 'useRecentPosts', () => {
10
+ beforeEach( () => {
11
+ jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( [] ) );
12
+ } );
13
+
14
+ afterEach( () => {
15
+ jest.clearAllMocks();
16
+ } );
17
+
18
+ it( 'should return an array of posts when the request succeeds', async () => {
19
+ const posts = [
20
+ {
21
+ id: 1,
22
+ title: 'Post 1',
23
+ },
24
+ {
25
+ id: 2,
26
+ title: 'Post 2',
27
+ },
28
+ ];
29
+
30
+ jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( posts ) );
31
+
32
+ const { result } = renderHook( () => useRecentPosts( 1 ) );
33
+
34
+ await waitFor( () => {
35
+ expect( apiFetch ).toHaveBeenCalledWith( {
36
+ path: endpointPath + '?posts_per_page=5&post__not_in=1',
37
+ } );
38
+
39
+ expect( result.current.recentPosts ).toBe( posts );
40
+ } );
41
+ } );
42
+ } );
@@ -0,0 +1,42 @@
1
+ import apiFetch from '@wordpress/api-fetch';
2
+ import { useState } from 'react';
3
+
4
+ export interface NewPost {
5
+ id: number,
6
+ edit_url: string,
7
+ }
8
+
9
+ export type Args = {
10
+ onCreated: ( url : string ) => void;
11
+ }
12
+
13
+ export const endpointPath = '/elementor/v1/site-navigation/add-new-post';
14
+
15
+ export default function useCreatePage( { onCreated }: Args ) {
16
+ const [ isLoading, setIsLoading ] = useState( false );
17
+
18
+ return {
19
+ create: () => {
20
+ setIsLoading( true );
21
+
22
+ addNewPage()
23
+ .then( ( newPost ) => newPost as NewPost )
24
+ .then( ( newPost ) => {
25
+ setIsLoading( false );
26
+ onCreated( newPost.edit_url );
27
+ } )
28
+ .catch( () => {
29
+ setIsLoading( false );
30
+ } );
31
+ },
32
+ isLoading,
33
+ };
34
+ }
35
+
36
+ async function addNewPage() {
37
+ return await apiFetch( {
38
+ path: endpointPath,
39
+ method: 'POST',
40
+ data: { post_type: 'page' },
41
+ } );
42
+ }
@@ -1,5 +1,4 @@
1
1
  import { useEffect, useState } from 'react';
2
- import { DocType } from '../types';
3
2
  import apiFetch from '@wordpress/api-fetch';
4
3
  import { addQueryArgs } from '@wordpress/url';
5
4
 
@@ -9,12 +8,14 @@ export interface Post {
9
8
  edit_url: string,
10
9
  type: {
11
10
  post_type: string,
12
- doc_type: DocType,
11
+ doc_type: string,
13
12
  label: string,
14
13
  },
15
14
  date_modified: number,
16
15
  }
17
16
 
17
+ export const endpointPath = '/elementor/v1/site-navigation/recent-posts';
18
+
18
19
  export default function useRecentPosts( documentId?: number ) {
19
20
  const [ recentPosts, setRecentPosts ] = useState<Post[]>( [] );
20
21
  const [ isLoading, setIsLoading ] = useState( false );
@@ -43,7 +44,7 @@ async function fetchRecentlyEditedPosts( documentId: number ) {
43
44
  };
44
45
 
45
46
  return await apiFetch( {
46
- path: addQueryArgs( '/elementor/v1/site-navigation/recent-posts', queryParams ),
47
+ path: addQueryArgs( endpointPath, queryParams ),
47
48
  } ).then( ( response ) => response as Post[] )
48
49
  .catch( () => [] );
49
50
  }
@@ -0,0 +1,35 @@
1
+ import {
2
+ PostTypeIcon,
3
+ PageTypeIcon,
4
+ PageTemplateIcon,
5
+ SectionTemplateIcon,
6
+ ContainerTemplateIcon,
7
+ } from '@elementor/icons';
8
+
9
+ import * as React from 'react';
10
+
11
+ type IconsMap = {
12
+ [key: string]: React.ElementType;
13
+ };
14
+
15
+ const initialIconsMap: IconsMap = {
16
+ page: PageTemplateIcon,
17
+ section: SectionTemplateIcon,
18
+ container: ContainerTemplateIcon,
19
+ 'wp-page': PageTypeIcon,
20
+ 'wp-post': PostTypeIcon,
21
+ };
22
+
23
+ let iconsMap = { ...initialIconsMap };
24
+
25
+ export function extendIconsMap( additionalIcons: IconsMap ) {
26
+ Object.assign( iconsMap, additionalIcons );
27
+ }
28
+
29
+ export function getIconsMap() {
30
+ return iconsMap;
31
+ }
32
+
33
+ export function resetIconsMap() {
34
+ iconsMap = { ...initialIconsMap };
35
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export { extendIconsMap } from './icons-map';
2
+
1
3
  import init from './init';
2
4
 
3
5
  init();
package/src/types.ts DELETED
@@ -1 +0,0 @@
1
- export type DocType = 'header' | 'footer' | 'single-post' | 'single-page' | 'popup' | 'archive' | 'search-results' | 'loop-item' | 'error-404' | 'landing-page' | 'page' | 'section' | 'container' | 'wp-page' | 'wp-post';