PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / Agent / abilities / navigation.js

navigation.js in Extendify 3.2.1, at src/Agent/abilities/navigation.js

108 lines 2.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import apiFetch from '@wordpress/api-fetch';
2 import { decodeEntities } from '@wordpress/html-entities';
3 import { __ } from '@wordpress/i18n';
4
5 const path = '/extendify/v1/agent/site-navigation';
6
7 const blockPattern = /<!--\s+(\/)?wp:([\w/-]+)\s*(\{.*?\})?\s*(\/)?-->/g;
8 const navigationPattern = /<!--\s+wp:navigation\s+(\{.*?\})\s*\/?-->/g;
9
10 const attributes = (json) => {
11 if (!json) return {};
12 try {
13 return JSON.parse(json);
14 } catch {
15 return {};
16 }
17 };
18
19 const withoutEmpties = (items) =>
20 items.map(({ items: children, ...item }) =>
21 children?.length ? { ...item, items: withoutEmpties(children) } : item,
22 );
23
24 const entries = (content = '') => {
25 const root = { items: [] };
26 const open = [root];
27 for (const [, closing, name, json, selfClosing] of content.matchAll(
28 blockPattern,
29 )) {
30 if (closing) {
31 if (open.length > 1) open.pop();
32 continue;
33 }
34
35 const { label, url } = attributes(json);
36 const item = {
37 type: name.replace('core/', ''),
38 ...(label && { label: decodeEntities(label) }),
39 ...(url && { url }),
40 };
41 open.at(-1).items.push(item);
42 if (!selfClosing) {
43 item.items = [];
44 open.push(item);
45 }
46 }
47
48 return withoutEmpties(root.items);
49 };
50
51 const menusRenderedIn = async (location) => {
52 const parts = await apiFetch({ path: '/wp/v2/template-parts?context=edit' });
53
54 return parts
55 .filter((part) => part.area === location)
56 .flatMap((part) => [
57 ...(part.content?.raw ?? '').matchAll(navigationPattern),
58 ])
59 .map(([, json]) => attributes(json).ref)
60 .filter(Boolean);
61 };
62
63 export const navigation = {
64 name: 'extendify/navigation',
65 label: __('Menus', 'extendify-local'),
66 description:
67 'The menus this site has and what each one links to, as a list rather than the markup it is stored as. Ask for a place on the page for the menu a visitor sees there, or for a menu by id.',
68 inputSchema: {
69 type: 'object',
70 properties: {
71 location: {
72 type: 'string',
73 enum: ['header', 'footer'],
74 description: 'Where on the page the menu is shown.',
75 },
76 menuId: {
77 type: 'integer',
78 description: 'One menu, by the id another tool returned.',
79 },
80 },
81 },
82 annotations: { readonly: true },
83 execute: async ({ location, menuId } = {}) => {
84 const ids = menuId
85 ? [menuId]
86 : location
87 ? await menusRenderedIn(location)
88 : [];
89 if (location && !ids.length) {
90 return { error: `No menu is rendered in the ${location}.` };
91 }
92
93 const menus = await apiFetch({
94 path,
95 method: 'POST',
96 data: ids.length ? { only: ids.join(',') } : {},
97 });
98
99 return {
100 menus: menus.map(({ id, name, content }) => ({
101 id,
102 name: decodeEntities(name),
103 items: entries(content),
104 })),
105 };
106 },
107 };
108