@newcms/core 0.1.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/dist/index.cjs +1254 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +697 -0
- package/dist/index.d.ts +697 -6
- package/dist/index.js +1211 -4
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
- package/dist/hook-engine.d.ts +0 -134
- package/dist/hook-engine.d.ts.map +0 -1
- package/dist/hook-engine.js +0 -370
- package/dist/hook-engine.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/post-type-registry.d.ts +0 -50
- package/dist/post-type-registry.d.ts.map +0 -1
- package/dist/post-type-registry.js +0 -159
- package/dist/post-type-registry.js.map +0 -1
- package/dist/taxonomy-registry.d.ts +0 -20
- package/dist/taxonomy-registry.d.ts.map +0 -1
- package/dist/taxonomy-registry.js +0 -71
- package/dist/taxonomy-registry.js.map +0 -1
- package/dist/types.d.ts +0 -124
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -34
- package/dist/types.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,1212 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// src/hook-engine.ts
|
|
2
|
+
var anonymousCounter = 0;
|
|
3
|
+
function generateCallbackId(callback, priority) {
|
|
4
|
+
if (callback.name && callback.name !== "") {
|
|
5
|
+
return `${callback.name}::${priority}`;
|
|
6
|
+
}
|
|
7
|
+
anonymousCounter++;
|
|
8
|
+
return `__anonymous_${anonymousCounter}::${priority}`;
|
|
9
|
+
}
|
|
10
|
+
var HookEngine = class {
|
|
11
|
+
/**
|
|
12
|
+
* Map of hook name → array of handlers, kept sorted by priority.
|
|
13
|
+
*/
|
|
14
|
+
hooks = /* @__PURE__ */ new Map();
|
|
15
|
+
/**
|
|
16
|
+
* Stack of hooks currently being executed (supports recursion).
|
|
17
|
+
*/
|
|
18
|
+
currentStack = [];
|
|
19
|
+
/**
|
|
20
|
+
* Counter of how many times each hook has been fired.
|
|
21
|
+
*/
|
|
22
|
+
fireCount = /* @__PURE__ */ new Map();
|
|
23
|
+
/**
|
|
24
|
+
* Register a callback for a hook.
|
|
25
|
+
*
|
|
26
|
+
* @param hookName - The hook identifier
|
|
27
|
+
* @param callback - The function to execute
|
|
28
|
+
* @param options - Priority and accepted args configuration
|
|
29
|
+
* @returns The generated handler ID
|
|
30
|
+
*/
|
|
31
|
+
addHook(hookName, callback, options = {}) {
|
|
32
|
+
const priority = options.priority ?? 10;
|
|
33
|
+
const acceptedArgs = options.acceptedArgs ?? 1;
|
|
34
|
+
const id = generateCallbackId(callback, priority);
|
|
35
|
+
const handler = {
|
|
36
|
+
callback,
|
|
37
|
+
priority,
|
|
38
|
+
acceptedArgs,
|
|
39
|
+
id
|
|
40
|
+
};
|
|
41
|
+
const existing = this.hooks.get(hookName) ?? [];
|
|
42
|
+
existing.push(handler);
|
|
43
|
+
existing.sort((a, b) => a.priority - b.priority);
|
|
44
|
+
this.hooks.set(hookName, existing);
|
|
45
|
+
return id;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Remove a specific callback from a hook.
|
|
49
|
+
*
|
|
50
|
+
* Both the callback reference AND priority must match for removal.
|
|
51
|
+
*
|
|
52
|
+
* @param hookName - The hook identifier
|
|
53
|
+
* @param callback - The callback to remove
|
|
54
|
+
* @param priority - The priority it was registered with (default: 10)
|
|
55
|
+
* @returns true if a handler was removed
|
|
56
|
+
*/
|
|
57
|
+
removeHook(hookName, callback, priority = 10) {
|
|
58
|
+
const handlers = this.hooks.get(hookName);
|
|
59
|
+
if (!handlers) return false;
|
|
60
|
+
const initialLength = handlers.length;
|
|
61
|
+
const filtered = handlers.filter(
|
|
62
|
+
(h) => !(h.callback === callback && h.priority === priority)
|
|
63
|
+
);
|
|
64
|
+
if (filtered.length === initialLength) return false;
|
|
65
|
+
if (filtered.length === 0) {
|
|
66
|
+
this.hooks.delete(hookName);
|
|
67
|
+
} else {
|
|
68
|
+
this.hooks.set(hookName, filtered);
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Remove all callbacks from a hook, optionally only for a specific priority.
|
|
74
|
+
*
|
|
75
|
+
* @param hookName - The hook identifier
|
|
76
|
+
* @param priority - If provided, only remove handlers at this priority
|
|
77
|
+
* @returns true if any handlers were removed
|
|
78
|
+
*/
|
|
79
|
+
removeAllHooks(hookName, priority) {
|
|
80
|
+
if (priority === void 0) {
|
|
81
|
+
return this.hooks.delete(hookName);
|
|
82
|
+
}
|
|
83
|
+
const handlers = this.hooks.get(hookName);
|
|
84
|
+
if (!handlers) return false;
|
|
85
|
+
const filtered = handlers.filter((h) => h.priority !== priority);
|
|
86
|
+
if (filtered.length === handlers.length) return false;
|
|
87
|
+
if (filtered.length === 0) {
|
|
88
|
+
this.hooks.delete(hookName);
|
|
89
|
+
} else {
|
|
90
|
+
this.hooks.set(hookName, filtered);
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if a hook has registered handlers.
|
|
96
|
+
*
|
|
97
|
+
* @param hookName - The hook identifier
|
|
98
|
+
* @param callback - If provided, check for this specific callback
|
|
99
|
+
* @returns false if no handlers, or the priority of the matching handler
|
|
100
|
+
*/
|
|
101
|
+
hasHook(hookName, callback) {
|
|
102
|
+
const handlers = this.hooks.get(hookName);
|
|
103
|
+
if (!handlers || handlers.length === 0) return false;
|
|
104
|
+
if (callback === void 0) {
|
|
105
|
+
return handlers[0].priority;
|
|
106
|
+
}
|
|
107
|
+
const found = handlers.find((h) => h.callback === callback);
|
|
108
|
+
return found ? found.priority : false;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Execute an action hook. All registered callbacks are called in priority order.
|
|
112
|
+
* The "all" universal hook fires before the specific hook.
|
|
113
|
+
*
|
|
114
|
+
* @param hookName - The hook identifier
|
|
115
|
+
* @param args - Arguments to pass to callbacks
|
|
116
|
+
*/
|
|
117
|
+
async doAction(hookName, ...args) {
|
|
118
|
+
if (hookName !== "all") {
|
|
119
|
+
await this.fireUniversalHook(hookName, args);
|
|
120
|
+
}
|
|
121
|
+
const handlers = this.hooks.get(hookName);
|
|
122
|
+
this.incrementFireCount(hookName);
|
|
123
|
+
if (!handlers || handlers.length === 0) return;
|
|
124
|
+
const stackEntry = { name: hookName, currentIndex: 0 };
|
|
125
|
+
this.currentStack.push(stackEntry);
|
|
126
|
+
try {
|
|
127
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
128
|
+
stackEntry.currentIndex = i;
|
|
129
|
+
const handler = handlers[i];
|
|
130
|
+
const slicedArgs = args.slice(0, handler.acceptedArgs);
|
|
131
|
+
await handler.callback(...slicedArgs);
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
this.currentStack.pop();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Execute an action hook synchronously.
|
|
139
|
+
*/
|
|
140
|
+
doActionSync(hookName, ...args) {
|
|
141
|
+
if (hookName !== "all") {
|
|
142
|
+
this.fireUniversalHookSync(hookName, args);
|
|
143
|
+
}
|
|
144
|
+
const handlers = this.hooks.get(hookName);
|
|
145
|
+
this.incrementFireCount(hookName);
|
|
146
|
+
if (!handlers || handlers.length === 0) return;
|
|
147
|
+
const stackEntry = { name: hookName, currentIndex: 0 };
|
|
148
|
+
this.currentStack.push(stackEntry);
|
|
149
|
+
try {
|
|
150
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
151
|
+
stackEntry.currentIndex = i;
|
|
152
|
+
const handler = handlers[i];
|
|
153
|
+
const slicedArgs = args.slice(0, handler.acceptedArgs);
|
|
154
|
+
handler.callback(...slicedArgs);
|
|
155
|
+
}
|
|
156
|
+
} finally {
|
|
157
|
+
this.currentStack.pop();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Execute a filter hook. The first argument is the value being filtered.
|
|
162
|
+
* Each callback receives the (possibly modified) value and returns a new value.
|
|
163
|
+
* The "all" universal hook fires before the specific hook.
|
|
164
|
+
*
|
|
165
|
+
* @param hookName - The hook identifier
|
|
166
|
+
* @param value - The initial value to filter
|
|
167
|
+
* @param args - Additional arguments passed to each callback
|
|
168
|
+
* @returns The filtered value after all callbacks have processed it
|
|
169
|
+
*/
|
|
170
|
+
async applyFilters(hookName, value, ...args) {
|
|
171
|
+
if (hookName !== "all") {
|
|
172
|
+
await this.fireUniversalHook(hookName, [value, ...args]);
|
|
173
|
+
}
|
|
174
|
+
const handlers = this.hooks.get(hookName);
|
|
175
|
+
this.incrementFireCount(hookName);
|
|
176
|
+
if (!handlers || handlers.length === 0) return value;
|
|
177
|
+
const stackEntry = { name: hookName, currentIndex: 0 };
|
|
178
|
+
this.currentStack.push(stackEntry);
|
|
179
|
+
let filteredValue = value;
|
|
180
|
+
try {
|
|
181
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
182
|
+
stackEntry.currentIndex = i;
|
|
183
|
+
const handler = handlers[i];
|
|
184
|
+
const callArgs = [filteredValue, ...args].slice(0, handler.acceptedArgs);
|
|
185
|
+
filteredValue = await handler.callback(filteredValue, ...callArgs.slice(1));
|
|
186
|
+
}
|
|
187
|
+
} finally {
|
|
188
|
+
this.currentStack.pop();
|
|
189
|
+
}
|
|
190
|
+
return filteredValue;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Execute a filter hook synchronously.
|
|
194
|
+
*/
|
|
195
|
+
applyFiltersSync(hookName, value, ...args) {
|
|
196
|
+
if (hookName !== "all") {
|
|
197
|
+
this.fireUniversalHookSync(hookName, [value, ...args]);
|
|
198
|
+
}
|
|
199
|
+
const handlers = this.hooks.get(hookName);
|
|
200
|
+
this.incrementFireCount(hookName);
|
|
201
|
+
if (!handlers || handlers.length === 0) return value;
|
|
202
|
+
const stackEntry = { name: hookName, currentIndex: 0 };
|
|
203
|
+
this.currentStack.push(stackEntry);
|
|
204
|
+
let filteredValue = value;
|
|
205
|
+
try {
|
|
206
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
207
|
+
stackEntry.currentIndex = i;
|
|
208
|
+
const handler = handlers[i];
|
|
209
|
+
const callArgs = [filteredValue, ...args].slice(0, handler.acceptedArgs);
|
|
210
|
+
filteredValue = handler.callback(filteredValue, ...callArgs.slice(1));
|
|
211
|
+
}
|
|
212
|
+
} finally {
|
|
213
|
+
this.currentStack.pop();
|
|
214
|
+
}
|
|
215
|
+
return filteredValue;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Get how many times a hook has been fired.
|
|
219
|
+
*/
|
|
220
|
+
getFireCount(hookName) {
|
|
221
|
+
return this.fireCount.get(hookName) ?? 0;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Check if a specific hook is currently being executed.
|
|
225
|
+
*/
|
|
226
|
+
isDoingHook(hookName) {
|
|
227
|
+
if (hookName === void 0) {
|
|
228
|
+
return this.currentStack.length > 0;
|
|
229
|
+
}
|
|
230
|
+
return this.currentStack.some((entry) => entry.name === hookName);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get the name of the hook currently being executed (top of stack).
|
|
234
|
+
* Returns undefined if no hook is executing.
|
|
235
|
+
*/
|
|
236
|
+
currentHook() {
|
|
237
|
+
if (this.currentStack.length === 0) return void 0;
|
|
238
|
+
return this.currentStack[this.currentStack.length - 1].name;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get a snapshot of the current execution stack.
|
|
242
|
+
*/
|
|
243
|
+
getExecutionStack() {
|
|
244
|
+
return [...this.currentStack];
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Check if a hook has ever been fired (fire count > 0).
|
|
248
|
+
*/
|
|
249
|
+
didHook(hookName) {
|
|
250
|
+
return this.getFireCount(hookName) > 0;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Get the number of handlers registered for a hook.
|
|
254
|
+
*/
|
|
255
|
+
getHandlerCount(hookName) {
|
|
256
|
+
return this.hooks.get(hookName)?.length ?? 0;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Reset the engine — useful for testing.
|
|
260
|
+
*/
|
|
261
|
+
reset() {
|
|
262
|
+
this.hooks.clear();
|
|
263
|
+
this.currentStack = [];
|
|
264
|
+
this.fireCount.clear();
|
|
265
|
+
anonymousCounter = 0;
|
|
266
|
+
}
|
|
267
|
+
// --- Convenience aliases matching WordPress API names ---
|
|
268
|
+
addAction(hookName, callback, options) {
|
|
269
|
+
return this.addHook(hookName, callback, options);
|
|
270
|
+
}
|
|
271
|
+
addFilter(hookName, callback, options) {
|
|
272
|
+
return this.addHook(hookName, callback, options);
|
|
273
|
+
}
|
|
274
|
+
removeAction(hookName, callback, priority) {
|
|
275
|
+
return this.removeHook(hookName, callback, priority);
|
|
276
|
+
}
|
|
277
|
+
removeFilter(hookName, callback, priority) {
|
|
278
|
+
return this.removeHook(hookName, callback, priority);
|
|
279
|
+
}
|
|
280
|
+
hasAction(hookName, callback) {
|
|
281
|
+
return this.hasHook(hookName, callback);
|
|
282
|
+
}
|
|
283
|
+
hasFilter(hookName, callback) {
|
|
284
|
+
return this.hasHook(hookName, callback);
|
|
285
|
+
}
|
|
286
|
+
didAction(hookName) {
|
|
287
|
+
return this.didHook(hookName);
|
|
288
|
+
}
|
|
289
|
+
didFilter(hookName) {
|
|
290
|
+
return this.didHook(hookName);
|
|
291
|
+
}
|
|
292
|
+
// --- Private helpers ---
|
|
293
|
+
incrementFireCount(hookName) {
|
|
294
|
+
this.fireCount.set(hookName, (this.fireCount.get(hookName) ?? 0) + 1);
|
|
295
|
+
}
|
|
296
|
+
async fireUniversalHook(hookName, args) {
|
|
297
|
+
const allHandlers = this.hooks.get("all");
|
|
298
|
+
if (!allHandlers || allHandlers.length === 0) return;
|
|
299
|
+
const stackEntry = { name: "all", currentIndex: 0 };
|
|
300
|
+
this.currentStack.push(stackEntry);
|
|
301
|
+
try {
|
|
302
|
+
for (let i = 0; i < allHandlers.length; i++) {
|
|
303
|
+
stackEntry.currentIndex = i;
|
|
304
|
+
const handler = allHandlers[i];
|
|
305
|
+
await handler.callback(hookName, ...args);
|
|
306
|
+
}
|
|
307
|
+
} finally {
|
|
308
|
+
this.currentStack.pop();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
fireUniversalHookSync(hookName, args) {
|
|
312
|
+
const allHandlers = this.hooks.get("all");
|
|
313
|
+
if (!allHandlers || allHandlers.length === 0) return;
|
|
314
|
+
const stackEntry = { name: "all", currentIndex: 0 };
|
|
315
|
+
this.currentStack.push(stackEntry);
|
|
316
|
+
try {
|
|
317
|
+
for (let i = 0; i < allHandlers.length; i++) {
|
|
318
|
+
stackEntry.currentIndex = i;
|
|
319
|
+
const handler = allHandlers[i];
|
|
320
|
+
handler.callback(hookName, ...args);
|
|
321
|
+
}
|
|
322
|
+
} finally {
|
|
323
|
+
this.currentStack.pop();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// src/post-type-registry.ts
|
|
329
|
+
var PostTypeRegistry = class {
|
|
330
|
+
types = /* @__PURE__ */ new Map();
|
|
331
|
+
/**
|
|
332
|
+
* Register a new post type.
|
|
333
|
+
*
|
|
334
|
+
* @throws If a type with the same name is already registered
|
|
335
|
+
*/
|
|
336
|
+
register(definition) {
|
|
337
|
+
if (this.types.has(definition.name)) {
|
|
338
|
+
throw new Error(`Post type "${definition.name}" is already registered.`);
|
|
339
|
+
}
|
|
340
|
+
this.types.set(definition.name, definition);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Get a post type definition by name.
|
|
344
|
+
*/
|
|
345
|
+
get(name) {
|
|
346
|
+
return this.types.get(name);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Check if a post type is registered.
|
|
350
|
+
*/
|
|
351
|
+
has(name) {
|
|
352
|
+
return this.types.has(name);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Get all registered post types.
|
|
356
|
+
*/
|
|
357
|
+
getAll() {
|
|
358
|
+
return [...this.types.values()];
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Get only public post types (for REST API, search, etc).
|
|
362
|
+
*/
|
|
363
|
+
getPublic() {
|
|
364
|
+
return this.getAll().filter((t) => t.public !== false);
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Get post types that are exposed via REST API.
|
|
368
|
+
*/
|
|
369
|
+
getRestVisible() {
|
|
370
|
+
return this.getAll().filter((t) => t.showInRest === true);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Unregister a post type. Only custom types can be unregistered.
|
|
374
|
+
*/
|
|
375
|
+
unregister(name) {
|
|
376
|
+
return this.types.delete(name);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Reset registry — for testing only.
|
|
380
|
+
*/
|
|
381
|
+
reset() {
|
|
382
|
+
this.types.clear();
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
var BUILTIN_POST_TYPES = [
|
|
386
|
+
{
|
|
387
|
+
name: "post",
|
|
388
|
+
label: "Posts",
|
|
389
|
+
labels: { singular: "Post", plural: "Posts" },
|
|
390
|
+
public: true,
|
|
391
|
+
hierarchical: false,
|
|
392
|
+
showInRest: true,
|
|
393
|
+
restBase: "posts",
|
|
394
|
+
supports: [
|
|
395
|
+
"title",
|
|
396
|
+
"editor",
|
|
397
|
+
"author",
|
|
398
|
+
"thumbnail",
|
|
399
|
+
"excerpt",
|
|
400
|
+
"trackbacks",
|
|
401
|
+
"custom-fields",
|
|
402
|
+
"comments",
|
|
403
|
+
"revisions",
|
|
404
|
+
"post-formats"
|
|
405
|
+
],
|
|
406
|
+
taxonomies: ["category", "post_tag"],
|
|
407
|
+
hasArchive: true,
|
|
408
|
+
rewrite: { slug: "", withFront: true },
|
|
409
|
+
menuPosition: 5,
|
|
410
|
+
capability_type: "post"
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: "page",
|
|
414
|
+
label: "Pages",
|
|
415
|
+
labels: { singular: "Page", plural: "Pages" },
|
|
416
|
+
public: true,
|
|
417
|
+
hierarchical: true,
|
|
418
|
+
showInRest: true,
|
|
419
|
+
restBase: "pages",
|
|
420
|
+
supports: [
|
|
421
|
+
"title",
|
|
422
|
+
"editor",
|
|
423
|
+
"author",
|
|
424
|
+
"thumbnail",
|
|
425
|
+
"page-attributes",
|
|
426
|
+
"custom-fields",
|
|
427
|
+
"comments",
|
|
428
|
+
"revisions"
|
|
429
|
+
],
|
|
430
|
+
taxonomies: [],
|
|
431
|
+
hasArchive: false,
|
|
432
|
+
rewrite: { slug: "", withFront: false },
|
|
433
|
+
menuPosition: 20,
|
|
434
|
+
capability_type: "page"
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: "attachment",
|
|
438
|
+
label: "Media",
|
|
439
|
+
labels: { singular: "Media", plural: "Media" },
|
|
440
|
+
public: true,
|
|
441
|
+
hierarchical: false,
|
|
442
|
+
showInRest: true,
|
|
443
|
+
restBase: "media",
|
|
444
|
+
supports: ["title", "author", "comments"],
|
|
445
|
+
taxonomies: [],
|
|
446
|
+
hasArchive: false,
|
|
447
|
+
rewrite: false,
|
|
448
|
+
capability_type: "post"
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: "revision",
|
|
452
|
+
label: "Revisions",
|
|
453
|
+
labels: { singular: "Revision", plural: "Revisions" },
|
|
454
|
+
public: false,
|
|
455
|
+
hierarchical: false,
|
|
456
|
+
showInRest: false,
|
|
457
|
+
supports: ["author"],
|
|
458
|
+
taxonomies: [],
|
|
459
|
+
hasArchive: false,
|
|
460
|
+
rewrite: false,
|
|
461
|
+
capability_type: "post"
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: "nav_menu_item",
|
|
465
|
+
label: "Navigation Menu Items",
|
|
466
|
+
labels: { singular: "Navigation Menu Item", plural: "Navigation Menu Items" },
|
|
467
|
+
public: false,
|
|
468
|
+
hierarchical: false,
|
|
469
|
+
showInRest: false,
|
|
470
|
+
supports: [],
|
|
471
|
+
taxonomies: ["nav_menu"],
|
|
472
|
+
hasArchive: false,
|
|
473
|
+
rewrite: false,
|
|
474
|
+
capability_type: "post"
|
|
475
|
+
}
|
|
476
|
+
];
|
|
477
|
+
|
|
478
|
+
// src/taxonomy-registry.ts
|
|
479
|
+
var TaxonomyRegistry = class {
|
|
480
|
+
taxonomies = /* @__PURE__ */ new Map();
|
|
481
|
+
register(definition) {
|
|
482
|
+
if (this.taxonomies.has(definition.name)) {
|
|
483
|
+
throw new Error(`Taxonomy "${definition.name}" is already registered.`);
|
|
484
|
+
}
|
|
485
|
+
this.taxonomies.set(definition.name, definition);
|
|
486
|
+
}
|
|
487
|
+
get(name) {
|
|
488
|
+
return this.taxonomies.get(name);
|
|
489
|
+
}
|
|
490
|
+
has(name) {
|
|
491
|
+
return this.taxonomies.has(name);
|
|
492
|
+
}
|
|
493
|
+
getAll() {
|
|
494
|
+
return [...this.taxonomies.values()];
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Get taxonomies assigned to a specific object type (e.g., 'post').
|
|
498
|
+
*/
|
|
499
|
+
getForObjectType(objectType) {
|
|
500
|
+
return this.getAll().filter((t) => t.objectTypes.includes(objectType));
|
|
501
|
+
}
|
|
502
|
+
getRestVisible() {
|
|
503
|
+
return this.getAll().filter((t) => t.showInRest === true);
|
|
504
|
+
}
|
|
505
|
+
unregister(name) {
|
|
506
|
+
return this.taxonomies.delete(name);
|
|
507
|
+
}
|
|
508
|
+
reset() {
|
|
509
|
+
this.taxonomies.clear();
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
var BUILTIN_TAXONOMIES = [
|
|
513
|
+
{
|
|
514
|
+
name: "category",
|
|
515
|
+
objectTypes: ["post"],
|
|
516
|
+
label: "Categories",
|
|
517
|
+
labels: { singular: "Category", plural: "Categories" },
|
|
518
|
+
public: true,
|
|
519
|
+
hierarchical: true,
|
|
520
|
+
showInRest: true,
|
|
521
|
+
restBase: "categories",
|
|
522
|
+
rewrite: { slug: "category", withFront: true, hierarchical: true }
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
name: "post_tag",
|
|
526
|
+
objectTypes: ["post"],
|
|
527
|
+
label: "Tags",
|
|
528
|
+
labels: { singular: "Tag", plural: "Tags" },
|
|
529
|
+
public: true,
|
|
530
|
+
hierarchical: false,
|
|
531
|
+
showInRest: true,
|
|
532
|
+
restBase: "tags",
|
|
533
|
+
rewrite: { slug: "tag", withFront: true }
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
name: "nav_menu",
|
|
537
|
+
objectTypes: ["nav_menu_item"],
|
|
538
|
+
label: "Navigation Menus",
|
|
539
|
+
labels: { singular: "Navigation Menu", plural: "Navigation Menus" },
|
|
540
|
+
public: false,
|
|
541
|
+
hierarchical: false,
|
|
542
|
+
showInRest: false,
|
|
543
|
+
rewrite: false
|
|
544
|
+
}
|
|
545
|
+
];
|
|
546
|
+
|
|
547
|
+
// src/extension-registry.ts
|
|
548
|
+
var ExtensionRegistry = class {
|
|
549
|
+
extensions = /* @__PURE__ */ new Map();
|
|
550
|
+
/**
|
|
551
|
+
* Register an extension manifest (discovery phase).
|
|
552
|
+
*/
|
|
553
|
+
register(manifest, path, status) {
|
|
554
|
+
this.extensions.set(manifest.slug, {
|
|
555
|
+
manifest,
|
|
556
|
+
status,
|
|
557
|
+
path,
|
|
558
|
+
activatedAt: status === "active" ? /* @__PURE__ */ new Date() : void 0
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
get(slug) {
|
|
562
|
+
return this.extensions.get(slug);
|
|
563
|
+
}
|
|
564
|
+
has(slug) {
|
|
565
|
+
return this.extensions.has(slug);
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Get all extensions by status.
|
|
569
|
+
*/
|
|
570
|
+
getByStatus(status) {
|
|
571
|
+
return [...this.extensions.values()].filter((e) => e.status === status);
|
|
572
|
+
}
|
|
573
|
+
getAll() {
|
|
574
|
+
return [...this.extensions.values()];
|
|
575
|
+
}
|
|
576
|
+
getActive() {
|
|
577
|
+
return this.getByStatus("active");
|
|
578
|
+
}
|
|
579
|
+
getMustUse() {
|
|
580
|
+
return this.getByStatus("must-use");
|
|
581
|
+
}
|
|
582
|
+
getPaused() {
|
|
583
|
+
return this.getByStatus("paused");
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Activate an extension. Checks dependencies first.
|
|
587
|
+
*
|
|
588
|
+
* @throws If dependencies are not met
|
|
589
|
+
*/
|
|
590
|
+
activate(slug) {
|
|
591
|
+
const entry = this.extensions.get(slug);
|
|
592
|
+
if (!entry) throw new Error(`Extension "${slug}" not found`);
|
|
593
|
+
if (entry.status === "active") return;
|
|
594
|
+
const unmet = this.getUnmetDependencies(slug);
|
|
595
|
+
if (unmet.length > 0) {
|
|
596
|
+
throw new Error(
|
|
597
|
+
`Cannot activate "${slug}": missing dependencies: ${unmet.join(", ")}`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
entry.status = "active";
|
|
601
|
+
entry.activatedAt = /* @__PURE__ */ new Date();
|
|
602
|
+
entry.pauseReason = void 0;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Deactivate an extension. Checks if other extensions depend on it.
|
|
606
|
+
*
|
|
607
|
+
* @throws If other active extensions depend on this one
|
|
608
|
+
*/
|
|
609
|
+
deactivate(slug) {
|
|
610
|
+
const entry = this.extensions.get(slug);
|
|
611
|
+
if (!entry) throw new Error(`Extension "${slug}" not found`);
|
|
612
|
+
if (entry.status === "inactive") return;
|
|
613
|
+
if (entry.status === "must-use") {
|
|
614
|
+
throw new Error(`Cannot deactivate must-use extension "${slug}"`);
|
|
615
|
+
}
|
|
616
|
+
const dependents = this.getDependents(slug);
|
|
617
|
+
if (dependents.length > 0) {
|
|
618
|
+
throw new Error(
|
|
619
|
+
`Cannot deactivate "${slug}": required by: ${dependents.map((d) => d.manifest.slug).join(", ")}`
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
entry.status = "inactive";
|
|
623
|
+
entry.activatedAt = void 0;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Pause an extension due to a fatal error (recovery mode).
|
|
627
|
+
*/
|
|
628
|
+
pause(slug, reason) {
|
|
629
|
+
const entry = this.extensions.get(slug);
|
|
630
|
+
if (!entry) return;
|
|
631
|
+
entry.status = "paused";
|
|
632
|
+
entry.pauseReason = reason;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Unpause an extension (resume from recovery mode).
|
|
636
|
+
*/
|
|
637
|
+
unpause(slug) {
|
|
638
|
+
const entry = this.extensions.get(slug);
|
|
639
|
+
if (!entry || entry.status !== "paused") return;
|
|
640
|
+
entry.status = "active";
|
|
641
|
+
entry.pauseReason = void 0;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Remove an extension from the registry entirely.
|
|
645
|
+
*/
|
|
646
|
+
unregister(slug) {
|
|
647
|
+
return this.extensions.delete(slug);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Get dependencies that are not active.
|
|
651
|
+
*/
|
|
652
|
+
getUnmetDependencies(slug) {
|
|
653
|
+
const entry = this.extensions.get(slug);
|
|
654
|
+
if (!entry) return [];
|
|
655
|
+
const deps = entry.manifest.dependencies ?? [];
|
|
656
|
+
return deps.filter((dep) => {
|
|
657
|
+
const depEntry = this.extensions.get(dep);
|
|
658
|
+
return !depEntry || depEntry.status !== "active" && depEntry.status !== "must-use";
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Get active extensions that depend on the given extension.
|
|
663
|
+
*/
|
|
664
|
+
getDependents(slug) {
|
|
665
|
+
return this.getActive().filter(
|
|
666
|
+
(entry) => entry.manifest.dependencies?.includes(slug)
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Check for circular dependencies starting from a slug.
|
|
671
|
+
*/
|
|
672
|
+
hasCircularDependency(slug, visited = /* @__PURE__ */ new Set()) {
|
|
673
|
+
if (visited.has(slug)) return true;
|
|
674
|
+
visited.add(slug);
|
|
675
|
+
const entry = this.extensions.get(slug);
|
|
676
|
+
if (!entry) return false;
|
|
677
|
+
for (const dep of entry.manifest.dependencies ?? []) {
|
|
678
|
+
if (this.hasCircularDependency(dep, new Set(visited))) return true;
|
|
679
|
+
}
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
reset() {
|
|
683
|
+
this.extensions.clear();
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// src/theme-registry.ts
|
|
688
|
+
var ThemeRegistry = class {
|
|
689
|
+
themes = /* @__PURE__ */ new Map();
|
|
690
|
+
activeSlug = null;
|
|
691
|
+
register(manifest, path) {
|
|
692
|
+
this.themes.set(manifest.slug, { manifest, path, active: false });
|
|
693
|
+
}
|
|
694
|
+
get(slug) {
|
|
695
|
+
return this.themes.get(slug);
|
|
696
|
+
}
|
|
697
|
+
getAll() {
|
|
698
|
+
return [...this.themes.values()];
|
|
699
|
+
}
|
|
700
|
+
getActive() {
|
|
701
|
+
if (!this.activeSlug) return void 0;
|
|
702
|
+
return this.themes.get(this.activeSlug);
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Activate a theme. Resolves parent theme for child themes.
|
|
706
|
+
*/
|
|
707
|
+
activate(slug) {
|
|
708
|
+
const theme = this.themes.get(slug);
|
|
709
|
+
if (!theme) throw new Error(`Theme "${slug}" not found`);
|
|
710
|
+
if (this.activeSlug) {
|
|
711
|
+
const current = this.themes.get(this.activeSlug);
|
|
712
|
+
if (current) current.active = false;
|
|
713
|
+
}
|
|
714
|
+
if (theme.manifest.parent) {
|
|
715
|
+
const parent = this.themes.get(theme.manifest.parent);
|
|
716
|
+
if (!parent) {
|
|
717
|
+
throw new Error(
|
|
718
|
+
`Parent theme "${theme.manifest.parent}" not found for child theme "${slug}"`
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
theme.parentTheme = parent;
|
|
722
|
+
}
|
|
723
|
+
theme.active = true;
|
|
724
|
+
this.activeSlug = slug;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Check if the active theme supports a feature.
|
|
728
|
+
*/
|
|
729
|
+
supports(feature) {
|
|
730
|
+
const active = this.getActive();
|
|
731
|
+
if (!active) return false;
|
|
732
|
+
const supports = active.manifest.supports;
|
|
733
|
+
if (!supports) return false;
|
|
734
|
+
const value = supports[feature];
|
|
735
|
+
if (value === void 0 || value === false) return false;
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
reset() {
|
|
739
|
+
this.themes.clear();
|
|
740
|
+
this.activeSlug = null;
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
function resolveTemplateHierarchy(ctx) {
|
|
744
|
+
switch (ctx.type) {
|
|
745
|
+
case "single":
|
|
746
|
+
return [
|
|
747
|
+
ctx.postType && ctx.slug ? `${ctx.postType}-${ctx.slug}` : null,
|
|
748
|
+
ctx.postType ?? null,
|
|
749
|
+
"singular",
|
|
750
|
+
"index"
|
|
751
|
+
].filter(Boolean);
|
|
752
|
+
case "page":
|
|
753
|
+
return [
|
|
754
|
+
ctx.customTemplate ?? null,
|
|
755
|
+
ctx.slug ? `page-${ctx.slug}` : null,
|
|
756
|
+
ctx.id ? `page-${ctx.id}` : null,
|
|
757
|
+
"page",
|
|
758
|
+
"singular",
|
|
759
|
+
"index"
|
|
760
|
+
].filter(Boolean);
|
|
761
|
+
case "category":
|
|
762
|
+
return [
|
|
763
|
+
ctx.slug ? `category-${ctx.slug}` : null,
|
|
764
|
+
ctx.id ? `category-${ctx.id}` : null,
|
|
765
|
+
"category",
|
|
766
|
+
"archive",
|
|
767
|
+
"index"
|
|
768
|
+
].filter(Boolean);
|
|
769
|
+
case "tag":
|
|
770
|
+
return [
|
|
771
|
+
ctx.slug ? `tag-${ctx.slug}` : null,
|
|
772
|
+
ctx.id ? `tag-${ctx.id}` : null,
|
|
773
|
+
"tag",
|
|
774
|
+
"archive",
|
|
775
|
+
"index"
|
|
776
|
+
].filter(Boolean);
|
|
777
|
+
case "taxonomy":
|
|
778
|
+
return [
|
|
779
|
+
ctx.taxonomy && ctx.term ? `taxonomy-${ctx.taxonomy}-${ctx.term}` : null,
|
|
780
|
+
ctx.taxonomy && ctx.id ? `taxonomy-${ctx.taxonomy}-${ctx.id}` : null,
|
|
781
|
+
ctx.taxonomy ? `taxonomy-${ctx.taxonomy}` : null,
|
|
782
|
+
"taxonomy",
|
|
783
|
+
"archive",
|
|
784
|
+
"index"
|
|
785
|
+
].filter(Boolean);
|
|
786
|
+
case "author":
|
|
787
|
+
return [
|
|
788
|
+
ctx.nicename ? `author-${ctx.nicename}` : null,
|
|
789
|
+
ctx.id ? `author-${ctx.id}` : null,
|
|
790
|
+
"author",
|
|
791
|
+
"archive",
|
|
792
|
+
"index"
|
|
793
|
+
].filter(Boolean);
|
|
794
|
+
case "date":
|
|
795
|
+
return ["date", "archive", "index"];
|
|
796
|
+
case "search":
|
|
797
|
+
return ["search", "index"];
|
|
798
|
+
case "404":
|
|
799
|
+
return ["404", "index"];
|
|
800
|
+
case "home":
|
|
801
|
+
return ["front-page", "home", "index"];
|
|
802
|
+
case "archive":
|
|
803
|
+
return ["archive", "index"];
|
|
804
|
+
case "attachment":
|
|
805
|
+
return [
|
|
806
|
+
ctx.mimeType && ctx.mimeSubtype ? `${ctx.mimeType}-${ctx.mimeSubtype}` : null,
|
|
807
|
+
ctx.mimeSubtype ?? null,
|
|
808
|
+
ctx.mimeType ?? null,
|
|
809
|
+
"attachment",
|
|
810
|
+
"single",
|
|
811
|
+
"index"
|
|
812
|
+
].filter(Boolean);
|
|
813
|
+
default:
|
|
814
|
+
return ["index"];
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/bootstrap.ts
|
|
819
|
+
var BOOTSTRAP_PHASES = [
|
|
820
|
+
"entry_point",
|
|
821
|
+
// 1. HTTP request received
|
|
822
|
+
"configuration",
|
|
823
|
+
// 2. Load config (DB, keys, flags)
|
|
824
|
+
"initial_constants",
|
|
825
|
+
// 3. Define constants, memory limits
|
|
826
|
+
"environment_check",
|
|
827
|
+
// 4. Validate runtime versions
|
|
828
|
+
"error_handling",
|
|
829
|
+
// 5. Register fatal error handler + recovery mode
|
|
830
|
+
"core_functions",
|
|
831
|
+
// 6. Load core utility modules
|
|
832
|
+
"database_connect",
|
|
833
|
+
// 7. Connect to database (+ drop-in check)
|
|
834
|
+
"object_cache",
|
|
835
|
+
// 8. Initialize cache system (+ drop-in check)
|
|
836
|
+
"default_filters",
|
|
837
|
+
// 9. Register all core hooks/filters
|
|
838
|
+
"must_use_extensions",
|
|
839
|
+
// 10. Load must-use extensions (alphabetical)
|
|
840
|
+
"regular_extensions",
|
|
841
|
+
// 11. Load active extensions (skip paused)
|
|
842
|
+
"overridable_functions",
|
|
843
|
+
// 12. Load auth/hash/nonce functions (skip if overridden)
|
|
844
|
+
"extensions_loaded",
|
|
845
|
+
// 13. Fire "extensions_loaded" hook
|
|
846
|
+
"global_objects",
|
|
847
|
+
// 14. Create query engine, rewrite, widgets, roles
|
|
848
|
+
"theme",
|
|
849
|
+
// 15. Load active theme (child → parent)
|
|
850
|
+
"init",
|
|
851
|
+
// 16. Fire "init" hook (register types, taxonomies, etc.)
|
|
852
|
+
"system_loaded"
|
|
853
|
+
// 17. Fire "cms_loaded" hook
|
|
854
|
+
];
|
|
855
|
+
var BootstrapManager = class {
|
|
856
|
+
constructor(hooks) {
|
|
857
|
+
this.hooks = hooks;
|
|
858
|
+
for (const phase of BOOTSTRAP_PHASES) {
|
|
859
|
+
this.phaseHandlers.set(phase, []);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
hooks;
|
|
863
|
+
phaseHandlers = /* @__PURE__ */ new Map();
|
|
864
|
+
completedPhases = /* @__PURE__ */ new Set();
|
|
865
|
+
currentPhase = null;
|
|
866
|
+
/**
|
|
867
|
+
* Register a handler for a bootstrap phase.
|
|
868
|
+
*/
|
|
869
|
+
on(phase, handler) {
|
|
870
|
+
const handlers = this.phaseHandlers.get(phase);
|
|
871
|
+
if (!handlers) throw new Error(`Unknown bootstrap phase: "${phase}"`);
|
|
872
|
+
handlers.push(handler);
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Execute all 17 bootstrap phases in order.
|
|
876
|
+
* For short-init mode, pass a phase to stop at.
|
|
877
|
+
*/
|
|
878
|
+
async run(stopAfter) {
|
|
879
|
+
for (const phase of BOOTSTRAP_PHASES) {
|
|
880
|
+
this.currentPhase = phase;
|
|
881
|
+
const handlers = this.phaseHandlers.get(phase) ?? [];
|
|
882
|
+
for (const handler of handlers) {
|
|
883
|
+
await handler();
|
|
884
|
+
}
|
|
885
|
+
await this.hooks.doAction(`bootstrap:${phase}`);
|
|
886
|
+
this.completedPhases.add(phase);
|
|
887
|
+
this.currentPhase = null;
|
|
888
|
+
if (stopAfter === phase) break;
|
|
889
|
+
}
|
|
890
|
+
if (!stopAfter || stopAfter === "system_loaded") {
|
|
891
|
+
await this.hooks.doAction("cms_loaded");
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Check if a phase has been completed.
|
|
896
|
+
*/
|
|
897
|
+
isPhaseComplete(phase) {
|
|
898
|
+
return this.completedPhases.has(phase);
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Get the phase currently being executed.
|
|
902
|
+
*/
|
|
903
|
+
getCurrentPhase() {
|
|
904
|
+
return this.currentPhase;
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Get all completed phases.
|
|
908
|
+
*/
|
|
909
|
+
getCompletedPhases() {
|
|
910
|
+
return [...this.completedPhases];
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Reset — for testing.
|
|
914
|
+
*/
|
|
915
|
+
reset() {
|
|
916
|
+
this.completedPhases.clear();
|
|
917
|
+
this.currentPhase = null;
|
|
918
|
+
for (const phase of BOOTSTRAP_PHASES) {
|
|
919
|
+
this.phaseHandlers.set(phase, []);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
// src/types.ts
|
|
925
|
+
var POST_STATUS = {
|
|
926
|
+
PUBLISH: "publish",
|
|
927
|
+
DRAFT: "draft",
|
|
928
|
+
PENDING: "pending",
|
|
929
|
+
PRIVATE: "private",
|
|
930
|
+
TRASH: "trash",
|
|
931
|
+
AUTO_DRAFT: "auto-draft",
|
|
932
|
+
INHERIT: "inherit",
|
|
933
|
+
FUTURE: "future"
|
|
934
|
+
};
|
|
935
|
+
var USER_ROLES = {
|
|
936
|
+
ADMINISTRATOR: "administrator",
|
|
937
|
+
EDITOR: "editor",
|
|
938
|
+
AUTHOR: "author",
|
|
939
|
+
CONTRIBUTOR: "contributor",
|
|
940
|
+
SUBSCRIBER: "subscriber"
|
|
941
|
+
};
|
|
942
|
+
var HOOK_PRIORITY = {
|
|
943
|
+
EARLIEST: 1,
|
|
944
|
+
EARLY: 5,
|
|
945
|
+
DEFAULT: 10,
|
|
946
|
+
LATE: 15,
|
|
947
|
+
LATEST: 20
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
// src/shortcode.ts
|
|
951
|
+
var ShortcodeRegistry = class {
|
|
952
|
+
handlers = /* @__PURE__ */ new Map();
|
|
953
|
+
register(tag, callback) {
|
|
954
|
+
this.handlers.set(tag, callback);
|
|
955
|
+
}
|
|
956
|
+
unregister(tag) {
|
|
957
|
+
return this.handlers.delete(tag);
|
|
958
|
+
}
|
|
959
|
+
has(tag) {
|
|
960
|
+
return this.handlers.has(tag);
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Process a string, replacing all registered shortcodes with their output.
|
|
964
|
+
* Supports nesting: inner shortcodes are processed first.
|
|
965
|
+
*/
|
|
966
|
+
process(content) {
|
|
967
|
+
if (this.handlers.size === 0) return content;
|
|
968
|
+
const tagPattern = [...this.handlers.keys()].map(escRegex).join("|");
|
|
969
|
+
const selfClosingRe = new RegExp(
|
|
970
|
+
`\\[(${tagPattern})(\\s[^\\]]*?)?\\s*\\/\\]`,
|
|
971
|
+
"g"
|
|
972
|
+
);
|
|
973
|
+
const enclosingRe = new RegExp(
|
|
974
|
+
`\\[(${tagPattern})(\\s[^\\]]*?)?\\]([\\s\\S]*?)\\[\\/\\1\\]`,
|
|
975
|
+
"g"
|
|
976
|
+
);
|
|
977
|
+
let result = content;
|
|
978
|
+
let prevResult = "";
|
|
979
|
+
let iterations = 0;
|
|
980
|
+
while (result !== prevResult && iterations < 10) {
|
|
981
|
+
prevResult = result;
|
|
982
|
+
result = result.replace(enclosingRe, (full, tag, attrStr, inner) => {
|
|
983
|
+
const handler = this.handlers.get(tag);
|
|
984
|
+
if (!handler) return full;
|
|
985
|
+
const attrs = parseShortcodeAttributes(attrStr?.trim() ?? "");
|
|
986
|
+
return handler(attrs, inner, tag);
|
|
987
|
+
});
|
|
988
|
+
iterations++;
|
|
989
|
+
}
|
|
990
|
+
result = result.replace(selfClosingRe, (full, tag, attrStr) => {
|
|
991
|
+
const handler = this.handlers.get(tag);
|
|
992
|
+
if (!handler) return full;
|
|
993
|
+
const attrs = parseShortcodeAttributes(attrStr?.trim() ?? "");
|
|
994
|
+
return handler(attrs, "", tag);
|
|
995
|
+
});
|
|
996
|
+
return result;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Strip all shortcodes from content (remove tags, keep content).
|
|
1000
|
+
*/
|
|
1001
|
+
strip(content) {
|
|
1002
|
+
const tagPattern = [...this.handlers.keys()].map(escRegex).join("|");
|
|
1003
|
+
if (!tagPattern) return content;
|
|
1004
|
+
let result = content;
|
|
1005
|
+
result = result.replace(
|
|
1006
|
+
new RegExp(`\\[(${tagPattern})(\\s[^\\]]*?)?\\]([\\s\\S]*?)\\[\\/\\1\\]`, "g"),
|
|
1007
|
+
"$3"
|
|
1008
|
+
);
|
|
1009
|
+
result = result.replace(
|
|
1010
|
+
new RegExp(`\\[(${tagPattern})(\\s[^\\]]*?)?\\s*\\/\\]`, "g"),
|
|
1011
|
+
""
|
|
1012
|
+
);
|
|
1013
|
+
return result;
|
|
1014
|
+
}
|
|
1015
|
+
getAll() {
|
|
1016
|
+
return [...this.handlers.keys()];
|
|
1017
|
+
}
|
|
1018
|
+
reset() {
|
|
1019
|
+
this.handlers.clear();
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
function parseShortcodeAttributes(attrStr) {
|
|
1023
|
+
const attrs = {};
|
|
1024
|
+
if (!attrStr) return attrs;
|
|
1025
|
+
const re = /([a-zA-Z_][-a-zA-Z0-9_]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+))|([a-zA-Z_][-a-zA-Z0-9_]*)/g;
|
|
1026
|
+
let match;
|
|
1027
|
+
while ((match = re.exec(attrStr)) !== null) {
|
|
1028
|
+
if (match[1]) {
|
|
1029
|
+
attrs[match[1]] = match[2] ?? match[3] ?? match[4] ?? "";
|
|
1030
|
+
} else if (match[5]) {
|
|
1031
|
+
attrs[match[5]] = "";
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return attrs;
|
|
1035
|
+
}
|
|
1036
|
+
function escRegex(s) {
|
|
1037
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// src/menu-registry.ts
|
|
1041
|
+
var MenuRegistry = class {
|
|
1042
|
+
locations = /* @__PURE__ */ new Map();
|
|
1043
|
+
/**
|
|
1044
|
+
* Register a menu location (e.g., "primary", "footer").
|
|
1045
|
+
*/
|
|
1046
|
+
registerLocation(name, description) {
|
|
1047
|
+
this.locations.set(name, { name, description });
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Unregister a menu location.
|
|
1051
|
+
*/
|
|
1052
|
+
unregisterLocation(name) {
|
|
1053
|
+
return this.locations.delete(name);
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Get all registered locations.
|
|
1057
|
+
*/
|
|
1058
|
+
getLocations() {
|
|
1059
|
+
return [...this.locations.values()];
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Check if a location is registered.
|
|
1063
|
+
*/
|
|
1064
|
+
hasLocation(name) {
|
|
1065
|
+
return this.locations.has(name);
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Build a tree from a flat list of menu items using parentId.
|
|
1069
|
+
*/
|
|
1070
|
+
static buildTree(items) {
|
|
1071
|
+
const map = /* @__PURE__ */ new Map();
|
|
1072
|
+
const roots = [];
|
|
1073
|
+
const sorted = [...items].sort((a, b) => a.menuOrder - b.menuOrder);
|
|
1074
|
+
for (const item of sorted) {
|
|
1075
|
+
map.set(item.id, { ...item, children: [] });
|
|
1076
|
+
}
|
|
1077
|
+
for (const item of sorted) {
|
|
1078
|
+
const node = map.get(item.id);
|
|
1079
|
+
if (item.parentId === 0) {
|
|
1080
|
+
roots.push(node);
|
|
1081
|
+
} else {
|
|
1082
|
+
const parent = map.get(item.parentId);
|
|
1083
|
+
if (parent) {
|
|
1084
|
+
parent.children = parent.children ?? [];
|
|
1085
|
+
parent.children.push(node);
|
|
1086
|
+
} else {
|
|
1087
|
+
roots.push(node);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
return roots;
|
|
1092
|
+
}
|
|
1093
|
+
reset() {
|
|
1094
|
+
this.locations.clear();
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
// src/url-rewrite.ts
|
|
1099
|
+
var STRUCTURE_TAGS = {
|
|
1100
|
+
"%year%": { regex: "(?<year>\\d{4})", queryVar: "year" },
|
|
1101
|
+
"%monthnum%": { regex: "(?<monthnum>\\d{2})", queryVar: "monthnum" },
|
|
1102
|
+
"%day%": { regex: "(?<day>\\d{2})", queryVar: "day" },
|
|
1103
|
+
"%hour%": { regex: "(?<hour>\\d{2})", queryVar: "hour" },
|
|
1104
|
+
"%minute%": { regex: "(?<minute>\\d{2})", queryVar: "minute" },
|
|
1105
|
+
"%second%": { regex: "(?<second>\\d{2})", queryVar: "second" },
|
|
1106
|
+
"%postname%": { regex: "(?<postname>[^/]+)", queryVar: "name" },
|
|
1107
|
+
"%post_id%": { regex: "(?<post_id>\\d+)", queryVar: "p" },
|
|
1108
|
+
"%category%": { regex: "(?<category>[^/]+)", queryVar: "category_name" },
|
|
1109
|
+
"%tag%": { regex: "(?<tag>[^/]+)", queryVar: "tag" },
|
|
1110
|
+
"%author%": { regex: "(?<author>[^/]+)", queryVar: "author_name" },
|
|
1111
|
+
"%pagename%": { regex: "(?<pagename>[^/]+)", queryVar: "pagename" }
|
|
1112
|
+
};
|
|
1113
|
+
var UrlRewriter = class {
|
|
1114
|
+
rules = [];
|
|
1115
|
+
/**
|
|
1116
|
+
* Add a rewrite rule.
|
|
1117
|
+
*/
|
|
1118
|
+
addRule(pattern, queryVars, source, priority = 10) {
|
|
1119
|
+
this.rules.push({ pattern, queryVars, source, priority });
|
|
1120
|
+
this.rules.sort((a, b) => a.priority - b.priority);
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Generate rules from a permalink structure string.
|
|
1124
|
+
* E.g., "/%year%/%monthnum%/%postname%/"
|
|
1125
|
+
*/
|
|
1126
|
+
addPermalinkStructure(structure, source = "post_permalink") {
|
|
1127
|
+
let regexStr = structure;
|
|
1128
|
+
const queryVars = [];
|
|
1129
|
+
for (const [tag, def] of Object.entries(STRUCTURE_TAGS)) {
|
|
1130
|
+
if (regexStr.includes(tag)) {
|
|
1131
|
+
regexStr = regexStr.replace(tag, def.regex);
|
|
1132
|
+
queryVars.push(def.queryVar);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
regexStr = regexStr.replace(/^\//, "").replace(/\/$/, "");
|
|
1136
|
+
const pattern = new RegExp(`^${regexStr}/?$`);
|
|
1137
|
+
this.addRule(pattern, queryVars, source, 5);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Add default rules for categories, tags, authors, search, pages, feeds.
|
|
1141
|
+
*/
|
|
1142
|
+
addDefaultRules() {
|
|
1143
|
+
this.addRule(/^category\/(?<category>[^/]+)\/?$/, ["category_name"], "category", 10);
|
|
1144
|
+
this.addRule(/^tag\/(?<tag>[^/]+)\/?$/, ["tag"], "tag", 10);
|
|
1145
|
+
this.addRule(/^author\/(?<author>[^/]+)\/?$/, ["author_name"], "author", 10);
|
|
1146
|
+
this.addRule(/^search\/(?<s>.+)\/?$/, ["s"], "search", 10);
|
|
1147
|
+
this.addRule(/^page\/(?<paged>\d+)\/?$/, ["paged"], "paging", 10);
|
|
1148
|
+
this.addRule(/^feed\/?(?<feed>rss2?|atom|rdf)?\/?$/, ["feed"], "feed", 10);
|
|
1149
|
+
this.addRule(/^(?<pagename>[^/]+)\/?$/, ["pagename"], "page", 99);
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Resolve a URL path to query variables.
|
|
1153
|
+
*/
|
|
1154
|
+
resolve(path) {
|
|
1155
|
+
const cleanPath = path.replace(/^\//, "").replace(/\/$/, "");
|
|
1156
|
+
for (const rule of this.rules) {
|
|
1157
|
+
const match = cleanPath.match(rule.pattern);
|
|
1158
|
+
if (match?.groups) {
|
|
1159
|
+
const queryVars = {};
|
|
1160
|
+
for (const [key, value] of Object.entries(match.groups)) {
|
|
1161
|
+
if (value !== void 0) {
|
|
1162
|
+
const tag = Object.entries(STRUCTURE_TAGS).find(
|
|
1163
|
+
([_, def]) => def.regex.includes(`<${key}>`)
|
|
1164
|
+
);
|
|
1165
|
+
queryVars[tag ? tag[1].queryVar : key] = value;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
return { matched: true, rule, queryVars };
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return { matched: false, queryVars: {} };
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Build a permalink URL from a structure and values.
|
|
1175
|
+
* E.g., buildPermalink("/%year%/%monthnum%/%postname%/", { year: "2026", monthnum: "04", postname: "hello" })
|
|
1176
|
+
* → "/2026/04/hello/"
|
|
1177
|
+
*/
|
|
1178
|
+
static buildPermalink(structure, values) {
|
|
1179
|
+
let result = structure;
|
|
1180
|
+
for (const [tag, def] of Object.entries(STRUCTURE_TAGS)) {
|
|
1181
|
+
if (result.includes(tag) && values[def.queryVar]) {
|
|
1182
|
+
result = result.replace(tag, values[def.queryVar]);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
return result;
|
|
1186
|
+
}
|
|
1187
|
+
getRules() {
|
|
1188
|
+
return [...this.rules];
|
|
1189
|
+
}
|
|
1190
|
+
reset() {
|
|
1191
|
+
this.rules = [];
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
export {
|
|
1195
|
+
BOOTSTRAP_PHASES,
|
|
1196
|
+
BUILTIN_POST_TYPES,
|
|
1197
|
+
BUILTIN_TAXONOMIES,
|
|
1198
|
+
BootstrapManager,
|
|
1199
|
+
ExtensionRegistry,
|
|
1200
|
+
HOOK_PRIORITY,
|
|
1201
|
+
HookEngine,
|
|
1202
|
+
MenuRegistry,
|
|
1203
|
+
POST_STATUS,
|
|
1204
|
+
PostTypeRegistry,
|
|
1205
|
+
ShortcodeRegistry,
|
|
1206
|
+
TaxonomyRegistry,
|
|
1207
|
+
ThemeRegistry,
|
|
1208
|
+
USER_ROLES,
|
|
1209
|
+
UrlRewriter,
|
|
1210
|
+
resolveTemplateHierarchy
|
|
1211
|
+
};
|
|
5
1212
|
//# sourceMappingURL=index.js.map
|