@wordpress/build 0.2.1-next.2f1c7c01b.0 → 0.3.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.
- package/.cache/tsconfig.tsbuildinfo +1 -1
- package/CHANGELOG.md +2 -0
- package/README.md +36 -2
- package/package.json +2 -2
- package/src/build.mjs +175 -13
- package/src/package-utils.mjs +1 -1
- package/src/php-generator.mjs +32 -109
- package/src/route-utils.mjs +38 -0
- package/templates/index.php.template +6 -0
- package/templates/page-wp-admin.php.template +252 -0
- package/templates/page.php.template +311 -0
- package/templates/pages.php.template +9 -0
- package/templates/routes-registration.php.template +90 -0
- package/templates/routes.php.template +0 -66
package/src/php-generator.mjs
CHANGED
|
@@ -9,7 +9,6 @@ import { fileURLToPath } from 'url';
|
|
|
9
9
|
* Internal dependencies
|
|
10
10
|
*/
|
|
11
11
|
import { getPackageInfoFromFile } from './package-utils.mjs';
|
|
12
|
-
import { getAllRoutes, getRouteFiles } from './route-utils.mjs';
|
|
13
12
|
|
|
14
13
|
const __dirname = path.dirname( fileURLToPath( import.meta.url ) );
|
|
15
14
|
|
|
@@ -41,133 +40,57 @@ export async function getPhpReplacements( rootDir ) {
|
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
/**
|
|
44
|
-
*
|
|
43
|
+
* Apply template replacements to a template string.
|
|
45
44
|
*
|
|
46
|
-
* @param {string}
|
|
47
|
-
* @param {string} outputPath Full output path.
|
|
45
|
+
* @param {string} template Template string with placeholders.
|
|
48
46
|
* @param {Record<string, string>} replacements Replacements object (e.g. {'{{PREFIX}}': 'gutenberg'}).
|
|
47
|
+
* @return {string} Template with replacements applied.
|
|
49
48
|
*/
|
|
50
|
-
export
|
|
51
|
-
templateName,
|
|
52
|
-
outputPath,
|
|
53
|
-
replacements
|
|
54
|
-
) {
|
|
55
|
-
// Templates directory
|
|
56
|
-
const templatesDir = path.join( __dirname, '..', 'templates' );
|
|
57
|
-
|
|
58
|
-
// Read template
|
|
59
|
-
const template = await readFile(
|
|
60
|
-
path.join( templatesDir, templateName ),
|
|
61
|
-
'utf8'
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
// Apply all replacements
|
|
49
|
+
export function applyTemplateReplacements( template, replacements ) {
|
|
65
50
|
let content = template;
|
|
66
51
|
for ( const [ placeholder, value ] of Object.entries( replacements ) ) {
|
|
67
52
|
content = content.replaceAll( placeholder, value );
|
|
68
53
|
}
|
|
69
|
-
|
|
70
|
-
// Write output file
|
|
71
|
-
await mkdir( path.dirname( outputPath ), { recursive: true } );
|
|
72
|
-
await writeFile( outputPath, content );
|
|
54
|
+
return content;
|
|
73
55
|
}
|
|
74
56
|
|
|
75
57
|
/**
|
|
76
|
-
*
|
|
58
|
+
* Render a template to a string with replacements.
|
|
77
59
|
*
|
|
78
|
-
* @param {string}
|
|
79
|
-
* @param {string}
|
|
80
|
-
* @
|
|
60
|
+
* @param {string} templateName Template file name.
|
|
61
|
+
* @param {Record<string, string>} replacements Replacements object (e.g. {'{{PREFIX}}': 'gutenberg'}).
|
|
62
|
+
* @return {Promise<string>} Rendered template string.
|
|
81
63
|
*/
|
|
82
|
-
export async function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if ( routeNames.length === 0 ) {
|
|
86
|
-
// No routes to register, skip generating routes registry
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Build routes array
|
|
91
|
-
const routes = routeNames.map( ( routeName ) => {
|
|
92
|
-
// Read package.json to get route path
|
|
93
|
-
const routePackageJson =
|
|
94
|
-
/** @type {import('./package-utils.mjs').RoutePackageJson} */ (
|
|
95
|
-
getPackageInfoFromFile(
|
|
96
|
-
path.join( rootDir, 'routes', routeName, 'package.json' )
|
|
97
|
-
)
|
|
98
|
-
);
|
|
99
|
-
const routePath = routePackageJson.route.path;
|
|
100
|
-
|
|
101
|
-
// Check if route.js exists
|
|
102
|
-
const routeFiles = getRouteFiles(
|
|
103
|
-
path.join( rootDir, 'routes', routeName )
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
name: routeName,
|
|
108
|
-
path: routePath,
|
|
109
|
-
has_route: routeFiles.hasRoute,
|
|
110
|
-
};
|
|
111
|
-
} );
|
|
112
|
-
|
|
113
|
-
// Generate PHP array entries
|
|
114
|
-
const routeEntries = routes
|
|
115
|
-
.map( ( route ) => {
|
|
116
|
-
const hasRouteStr = route.has_route ? 'true' : 'false';
|
|
117
|
-
return `\tarray(
|
|
118
|
-
'name' => '${ route.name }',
|
|
119
|
-
'path' => '${ route.path }',
|
|
120
|
-
'has_route' => ${ hasRouteStr },
|
|
121
|
-
)`;
|
|
122
|
-
} )
|
|
123
|
-
.join( ',\n' );
|
|
124
|
-
|
|
125
|
-
// Generate routes/index.php
|
|
126
|
-
const replacements = {
|
|
127
|
-
'{{PREFIX}}': prefix,
|
|
128
|
-
'{{ROUTES}}': routeEntries,
|
|
129
|
-
};
|
|
64
|
+
export async function renderTemplateToString( templateName, replacements ) {
|
|
65
|
+
// Templates directory
|
|
66
|
+
const templatesDir = path.join( __dirname, '..', 'templates' );
|
|
130
67
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
path.join(
|
|
134
|
-
|
|
68
|
+
// Read template
|
|
69
|
+
const template = await readFile(
|
|
70
|
+
path.join( templatesDir, templateName ),
|
|
71
|
+
'utf8'
|
|
135
72
|
);
|
|
73
|
+
|
|
74
|
+
// Apply replacements
|
|
75
|
+
return applyTemplateReplacements( template, replacements );
|
|
136
76
|
}
|
|
137
77
|
|
|
138
78
|
/**
|
|
139
|
-
* Generate
|
|
79
|
+
* Generate a PHP file from a template with replacements.
|
|
140
80
|
*
|
|
141
|
-
* @param {string}
|
|
142
|
-
* @param {string}
|
|
143
|
-
* @param {string}
|
|
144
|
-
* @param {string} prefix Package prefix.
|
|
81
|
+
* @param {string} templateName Template file name.
|
|
82
|
+
* @param {string} outputPath Full output path.
|
|
83
|
+
* @param {Record<string, string>} replacements Replacements object (e.g. {'{{PREFIX}}': 'gutenberg'}).
|
|
145
84
|
*/
|
|
146
|
-
export async function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
prefix
|
|
85
|
+
export async function generatePhpFromTemplate(
|
|
86
|
+
templateName,
|
|
87
|
+
outputPath,
|
|
88
|
+
replacements
|
|
151
89
|
) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if ( routeNames.length === 0 ) {
|
|
155
|
-
// No routes to register, skip generating routes.php
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const namespace = handlePrefix.replace( /-/g, '_' );
|
|
90
|
+
// Render template to string
|
|
91
|
+
const content = await renderTemplateToString( templateName, replacements );
|
|
160
92
|
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
'{{NAMESPACE}}': namespace,
|
|
165
|
-
'{{HANDLE_PREFIX}}': handlePrefix,
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
await generatePhpFromTemplate(
|
|
169
|
-
'routes.php.template',
|
|
170
|
-
path.join( buildDir, 'routes.php' ),
|
|
171
|
-
replacements
|
|
172
|
-
);
|
|
93
|
+
// Write output file
|
|
94
|
+
await mkdir( path.dirname( outputPath ), { recursive: true } );
|
|
95
|
+
await writeFile( outputPath, content );
|
|
173
96
|
}
|
package/src/route-utils.mjs
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
import { readdirSync } from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { getPackageInfoFromFile } from './package-utils.mjs';
|
|
11
|
+
|
|
7
12
|
/**
|
|
8
13
|
* Get all route names from the routes directory.
|
|
9
14
|
*
|
|
@@ -23,6 +28,39 @@ export function getAllRoutes( rootDir ) {
|
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
30
|
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} RouteMetadata
|
|
33
|
+
* @property {string} name Route name.
|
|
34
|
+
* @property {string} path Route path.
|
|
35
|
+
* @property {string|null} page Page slug this route belongs to.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get route metadata from package.json.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} rootDir Root directory of the project.
|
|
42
|
+
* @param {string} routeName Route name.
|
|
43
|
+
* @return {RouteMetadata|null} Route metadata object or null if not found.
|
|
44
|
+
*/
|
|
45
|
+
export function getRouteMetadata( rootDir, routeName ) {
|
|
46
|
+
const routePackageJson =
|
|
47
|
+
/** @type {import('./package-utils.mjs').RoutePackageJson|null} */ (
|
|
48
|
+
getPackageInfoFromFile(
|
|
49
|
+
path.join( rootDir, 'routes', routeName, 'package.json' )
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if ( ! routePackageJson || ! routePackageJson.route ) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
name: routeName,
|
|
59
|
+
path: routePackageJson.route.path,
|
|
60
|
+
page: routePackageJson.route.page || null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
26
64
|
/**
|
|
27
65
|
* @typedef {Object} RouteFiles
|
|
28
66
|
* @property {boolean} hasRoute Whether route file exists.
|
|
@@ -35,3 +35,9 @@ $routes_file = __DIR__ . '/routes.php';
|
|
|
35
35
|
if ( file_exists( $routes_file ) ) {
|
|
36
36
|
require_once $routes_file;
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
// Load pages registration.
|
|
40
|
+
$pages_file = __DIR__ . '/pages.php';
|
|
41
|
+
if ( file_exists( $pages_file ) ) {
|
|
42
|
+
require_once $pages_file;
|
|
43
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* Page: {{PAGE_SLUG}} (wp-admin integrated)
|
|
4
|
+
* Auto-generated by build process.
|
|
5
|
+
* Do not edit this file manually.
|
|
6
|
+
*
|
|
7
|
+
* This version integrates with the standard WordPress admin interface,
|
|
8
|
+
* keeping the wp-admin sidebar and scripts/styles intact.
|
|
9
|
+
*
|
|
10
|
+
* @package {{PREFIX}}
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Global storage for {{PAGE_SLUG}} routes and menu items
|
|
14
|
+
global ${{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_routes, ${{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_menu_items;
|
|
15
|
+
${{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_routes = array();
|
|
16
|
+
${{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_menu_items = array();
|
|
17
|
+
|
|
18
|
+
if ( ! function_exists( 'register_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_route' ) ) {
|
|
19
|
+
/**
|
|
20
|
+
* Register a route for the {{PAGE_SLUG}}-wp-admin page.
|
|
21
|
+
*
|
|
22
|
+
* @param string $path Route path (e.g., '/types/$type/edit/$id').
|
|
23
|
+
* @param string|null $content_module Script module ID for content (stage/inspector).
|
|
24
|
+
* @param string|null $route_module Script module ID for route lifecycle hooks.
|
|
25
|
+
*/
|
|
26
|
+
function register_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_route( $path, $content_module = null, $route_module = null ) {
|
|
27
|
+
global ${{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_routes;
|
|
28
|
+
|
|
29
|
+
$route = array( 'path' => $path );
|
|
30
|
+
if ( ! empty( $content_module ) ) {
|
|
31
|
+
$route['content_module'] = $content_module;
|
|
32
|
+
}
|
|
33
|
+
if ( ! empty( $route_module ) ) {
|
|
34
|
+
$route['route_module'] = $route_module;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
${{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_routes[] = $route;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if ( ! function_exists( 'register_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_menu_item' ) ) {
|
|
42
|
+
/**
|
|
43
|
+
* Register a menu item for the {{PAGE_SLUG}}-wp-admin page.
|
|
44
|
+
* Note: Menu items are registered but not displayed in single-page mode.
|
|
45
|
+
*
|
|
46
|
+
* @param string $id Menu item ID.
|
|
47
|
+
* @param string $label Display label.
|
|
48
|
+
* @param string $to Route path to navigate to.
|
|
49
|
+
* @param string $parent_id Optional. Parent menu item ID.
|
|
50
|
+
*/
|
|
51
|
+
function register_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_menu_item( $id, $label, $to, $parent_id = '' ) {
|
|
52
|
+
global ${{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_menu_items;
|
|
53
|
+
|
|
54
|
+
$menu_item = array(
|
|
55
|
+
'id' => $id,
|
|
56
|
+
'label' => $label,
|
|
57
|
+
'to' => $to,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if ( ! empty( $parent_id ) ) {
|
|
61
|
+
$menu_item['parent'] = $parent_id;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
${{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_menu_items[] = $menu_item;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if ( ! function_exists( 'get_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_routes' ) ) {
|
|
69
|
+
/**
|
|
70
|
+
* Get all registered routes for the {{PAGE_SLUG}}-wp-admin page.
|
|
71
|
+
*
|
|
72
|
+
* @return array Array of route objects.
|
|
73
|
+
*/
|
|
74
|
+
function get_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_routes() {
|
|
75
|
+
global ${{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_routes;
|
|
76
|
+
return ${{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_routes ?? array();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if ( ! function_exists( 'get_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_menu_items' ) ) {
|
|
81
|
+
/**
|
|
82
|
+
* Get all registered menu items for the {{PAGE_SLUG}}-wp-admin page.
|
|
83
|
+
*
|
|
84
|
+
* @return array Array of menu item objects.
|
|
85
|
+
*/
|
|
86
|
+
function get_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_menu_items() {
|
|
87
|
+
global ${{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_menu_items;
|
|
88
|
+
return ${{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_menu_items ?? array();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if ( ! function_exists( '{{PAGE_SLUG_UNDERSCORE}}_wp_admin_preload_data' ) ) {
|
|
93
|
+
/**
|
|
94
|
+
* Preload REST API data for the {{PAGE_SLUG}}-wp-admin page.
|
|
95
|
+
* Automatically called during page rendering.
|
|
96
|
+
*/
|
|
97
|
+
function {{PAGE_SLUG_UNDERSCORE}}_wp_admin_preload_data() {
|
|
98
|
+
// Define paths to preload - same for all pages
|
|
99
|
+
$preload_paths = array(
|
|
100
|
+
'/?_fields=description,gmt_offset,home,name,site_icon,site_icon_url,site_logo,timezone_string,url,page_for_posts,page_on_front,show_on_front',
|
|
101
|
+
array( '/wp/v2/settings', 'OPTIONS' ),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Use rest_preload_api_request to gather the preloaded data
|
|
105
|
+
$preload_data = array_reduce(
|
|
106
|
+
$preload_paths,
|
|
107
|
+
'rest_preload_api_request',
|
|
108
|
+
array()
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Register the preloading middleware with wp-api-fetch
|
|
112
|
+
wp_add_inline_script(
|
|
113
|
+
'wp-api-fetch',
|
|
114
|
+
sprintf(
|
|
115
|
+
'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );',
|
|
116
|
+
wp_json_encode( $preload_data )
|
|
117
|
+
),
|
|
118
|
+
'after'
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if ( ! function_exists( '{{PAGE_SLUG_UNDERSCORE}}_wp_admin_enqueue_scripts' ) ) {
|
|
124
|
+
/**
|
|
125
|
+
* Enqueue scripts and styles for the {{PAGE_SLUG}}-wp-admin page.
|
|
126
|
+
* Hooked to admin_enqueue_scripts.
|
|
127
|
+
*
|
|
128
|
+
* @param string $hook_suffix The current admin page.
|
|
129
|
+
*/
|
|
130
|
+
function {{PAGE_SLUG_UNDERSCORE}}_wp_admin_enqueue_scripts( $hook_suffix ) {
|
|
131
|
+
// Only enqueue on our page
|
|
132
|
+
if ( ! isset( $_GET['page'] ) || '{{PAGE_SLUG}}-wp-admin' !== $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Fire init action for extensions to register routes and menu items
|
|
137
|
+
do_action( '{{PAGE_SLUG}}-wp-admin_init' );
|
|
138
|
+
|
|
139
|
+
// Preload REST API data
|
|
140
|
+
{{PAGE_SLUG_UNDERSCORE}}_wp_admin_preload_data();
|
|
141
|
+
|
|
142
|
+
// Get all registered routes
|
|
143
|
+
$routes = get_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_routes();
|
|
144
|
+
|
|
145
|
+
// Get boot module asset file for dependencies
|
|
146
|
+
$asset_file = plugin_dir_path( __FILE__ ) . '../../modules/boot/index.min.asset.php';
|
|
147
|
+
if ( file_exists( $asset_file ) ) {
|
|
148
|
+
$asset = require $asset_file;
|
|
149
|
+
|
|
150
|
+
// This script serves two purposes:
|
|
151
|
+
// 1. It ensures all the globals that are made available to the modules are loaded.
|
|
152
|
+
// 2. It initializes the boot module as an inline script.
|
|
153
|
+
wp_register_script( '{{PAGE_SLUG}}-wp-admin-prerequisites', '', $asset['dependencies'], $asset['version'], true );
|
|
154
|
+
|
|
155
|
+
// Add inline script to initialize the app using initSinglePage (no menuItems)
|
|
156
|
+
wp_add_inline_script(
|
|
157
|
+
'{{PAGE_SLUG}}-wp-admin-prerequisites',
|
|
158
|
+
sprintf(
|
|
159
|
+
'import("@wordpress/boot").then(mod => mod.initSinglePage({mountId: "%s", routes: %s}));',
|
|
160
|
+
'{{PAGE_SLUG}}-wp-admin-app',
|
|
161
|
+
wp_json_encode( $routes, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES )
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Register prerequisites style by filtering script dependencies to find registered styles
|
|
166
|
+
$style_dependencies = array_filter(
|
|
167
|
+
$asset['dependencies'],
|
|
168
|
+
function ( $handle ) {
|
|
169
|
+
return wp_style_is( $handle, 'registered' );
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
wp_register_style( '{{PAGE_SLUG}}-wp-admin-prerequisites', false, $style_dependencies, $asset['version'] );
|
|
173
|
+
|
|
174
|
+
// Build dependencies for {{PAGE_SLUG}}-wp-admin module
|
|
175
|
+
$boot_dependencies = array(
|
|
176
|
+
array(
|
|
177
|
+
'import' => 'static',
|
|
178
|
+
'id' => '@wordpress/boot',
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Add all registered routes as dependencies
|
|
183
|
+
foreach ( $routes as $route ) {
|
|
184
|
+
if ( isset( $route['route_module'] ) ) {
|
|
185
|
+
$boot_dependencies[] = array(
|
|
186
|
+
'import' => 'static',
|
|
187
|
+
'id' => $route['route_module'],
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if ( isset( $route['content_module'] ) ) {
|
|
191
|
+
$boot_dependencies[] = array(
|
|
192
|
+
'import' => 'dynamic',
|
|
193
|
+
'id' => $route['content_module'],
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Dummy script module to ensure dependencies are loaded
|
|
199
|
+
wp_register_script_module(
|
|
200
|
+
'{{PAGE_SLUG}}-wp-admin',
|
|
201
|
+
plugin_dir_url( __FILE__ ) . 'loader.js',
|
|
202
|
+
$boot_dependencies
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Enqueue the boot scripts and styles
|
|
206
|
+
wp_enqueue_script( '{{PAGE_SLUG}}-wp-admin-prerequisites' );
|
|
207
|
+
wp_enqueue_script_module( '{{PAGE_SLUG}}-wp-admin' );
|
|
208
|
+
wp_enqueue_style( '{{PAGE_SLUG}}-wp-admin-prerequisites' );
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if ( ! function_exists( '{{PAGE_SLUG_UNDERSCORE}}_wp_admin_render_page' ) ) {
|
|
214
|
+
/**
|
|
215
|
+
* Render the {{PAGE_SLUG}}-wp-admin page.
|
|
216
|
+
* Call this function from add_menu_page or add_submenu_page.
|
|
217
|
+
* This renders within the normal WordPress admin interface.
|
|
218
|
+
*/
|
|
219
|
+
function {{PAGE_SLUG_UNDERSCORE}}_wp_admin_render_page() {
|
|
220
|
+
?>
|
|
221
|
+
<div id="{{PAGE_SLUG}}-wp-admin-app" class="boot-layout-container"></div>
|
|
222
|
+
<?php
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Hook the enqueue function to admin_enqueue_scripts
|
|
227
|
+
add_action( 'admin_enqueue_scripts', '{{PAGE_SLUG_UNDERSCORE}}_wp_admin_enqueue_scripts' );
|
|
228
|
+
|
|
229
|
+
if ( ! function_exists( '{{PAGE_SLUG_UNDERSCORE}}_wp_admin_register_page' ) ) {
|
|
230
|
+
/**
|
|
231
|
+
* Register the {{PAGE_SLUG}}-wp-admin base page handler.
|
|
232
|
+
* This is hidden from the menu but provides the page callback for URL routing.
|
|
233
|
+
* Visible menu items should use the full URL pattern with query parameters.
|
|
234
|
+
*
|
|
235
|
+
* Example:
|
|
236
|
+
* $url = admin_url( 'admin.php?page={{PAGE_SLUG}}-wp-admin&p=' . urlencode( '/your/route' ) );
|
|
237
|
+
* add_menu_page( 'Title', 'Menu', 'capability', $url, '', 'icon', 10 );
|
|
238
|
+
*/
|
|
239
|
+
function {{PAGE_SLUG_UNDERSCORE}}_wp_admin_register_page() {
|
|
240
|
+
add_submenu_page(
|
|
241
|
+
'nothing-{{PAGE_SLUG}}', // Hidden page (no parent)
|
|
242
|
+
__( '{{PAGE_SLUG}} (WP Admin)', '{{PREFIX}}' ), // Page title
|
|
243
|
+
__( '{{PAGE_SLUG}} (WP Admin)', '{{PREFIX}}' ), // Menu title (not visible)
|
|
244
|
+
'read', // Minimum capability
|
|
245
|
+
'{{PAGE_SLUG}}-wp-admin', // Menu slug for URL routing
|
|
246
|
+
'{{PAGE_SLUG_UNDERSCORE}}_wp_admin_render_page' // Callback function
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Hook the registration to admin_menu
|
|
252
|
+
add_action( 'admin_menu', '{{PAGE_SLUG_UNDERSCORE}}_wp_admin_register_page' );
|