-
Notifications
You must be signed in to change notification settings - Fork 4.3k
/
Copy pathresolvers.js
156 lines (139 loc) · 4.14 KB
/
resolvers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
* External dependencies
*/
import { groupBy, sortBy } from 'lodash';
/**
* WordPress dependencies
*/
import { parse, createBlock } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import {
NAVIGATION_POST_KIND,
NAVIGATION_POST_POST_TYPE,
} from '../utils/constants';
import { resolveMenuItems, dispatch } from './controls';
import { buildNavigationPostId } from './utils';
/**
* Creates a "stub" navigation post reflecting the contents of menu with id=menuId. The
* post is meant as a convenient to only exists in runtime and should never be saved. It
* enables a convenient way of editing the navigation by using a regular post editor.
*
* Fetches all menu items, converts them into blocks, and hydrates a new post with them.
*
* @param {number} menuId The id of menu to create a post from
* @return {void}
*/
export function* getNavigationPostForMenu( menuId ) {
if ( ! menuId ) {
return;
}
const stubPost = createStubPost( menuId );
// Persist an empty post to warm up the state
yield persistPost( stubPost );
// Dispatch startResolution to skip the execution of the real getEntityRecord resolver - it would
// issue an http request and fail.
const args = [
NAVIGATION_POST_KIND,
NAVIGATION_POST_POST_TYPE,
stubPost.id,
];
yield dispatch( 'core', 'startResolution', 'getEntityRecord', args );
// Now let's create a proper one hydrated using actual menu items
const menuItems = yield resolveMenuItems( menuId );
const [ navigationBlock, menuItemIdToClientId ] = createNavigationBlock(
menuItems
);
yield {
type: 'SET_MENU_ITEM_TO_CLIENT_ID_MAPPING',
postId: stubPost.id,
mapping: menuItemIdToClientId,
};
// Persist the actual post containing the navigation block
yield persistPost( createStubPost( menuId, navigationBlock ) );
// Dispatch finishResolution to conclude startResolution dispatched earlier
yield dispatch( 'core', 'finishResolution', 'getEntityRecord', args );
}
const createStubPost = ( menuId, navigationBlock = null ) => {
const id = buildNavigationPostId( menuId );
return {
id,
slug: id,
status: 'draft',
type: 'page',
blocks: navigationBlock ? [ navigationBlock ] : [],
meta: {
menuId,
},
};
};
const persistPost = ( post ) =>
dispatch(
'core',
'receiveEntityRecords',
NAVIGATION_POST_KIND,
NAVIGATION_POST_POST_TYPE,
post,
{ id: post.id },
false
);
/**
* Converts an adjacency list of menuItems into a navigation block.
*
* @param {Array} menuItems a list of menu items
* @return {Object} Navigation block
*/
function createNavigationBlock( menuItems ) {
const itemsByParentID = groupBy( menuItems, 'parent' );
const menuItemIdToClientId = {};
const menuItemsToTreeOfBlocks = ( items ) => {
const innerBlocks = [];
if ( ! items ) {
return;
}
const sortedItems = sortBy( items, 'menu_order' );
for ( const item of sortedItems ) {
let menuItemInnerBlocks = [];
if ( itemsByParentID[ item.id ]?.length ) {
menuItemInnerBlocks = menuItemsToTreeOfBlocks(
itemsByParentID[ item.id ]
);
}
const block = convertMenuItemToBlock( item, menuItemInnerBlocks );
menuItemIdToClientId[ item.id ] = block.clientId;
innerBlocks.push( block );
}
return innerBlocks;
};
// menuItemsToTreeOfBlocks takes an array of top-level menu items and recursively creates all their innerBlocks
const innerBlocks = menuItemsToTreeOfBlocks( itemsByParentID[ 0 ] || [] );
const navigationBlock = createBlock(
'core/navigation',
{
orientation: 'vertical',
},
innerBlocks
);
return [ navigationBlock, menuItemIdToClientId ];
}
function convertMenuItemToBlock( menuItem, innerBlocks = [] ) {
if ( menuItem.type === 'block' ) {
const [ block ] = parse( menuItem.content.raw );
if ( ! block ) {
return createBlock( 'core/freeform', {
originalContent: menuItem.content.raw,
} );
}
return createBlock( block.name, block.attributes, innerBlocks );
}
const attributes = {
label: menuItem.title.rendered,
url: menuItem.url,
title: menuItem.attr_title,
className: menuItem.classes.join( ' ' ),
description: menuItem.description,
rel: menuItem.xfn.join( ' ' ),
};
return createBlock( 'core/navigation-link', attributes, innerBlocks );
}