@wp-typia/project-tools 0.16.4 → 0.16.6
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/README.md +8 -6
- package/dist/runtime/built-in-block-code-artifacts.d.ts +8 -7
- package/dist/runtime/built-in-block-code-artifacts.js +27 -12
- package/dist/runtime/built-in-block-non-ts-artifacts.d.ts +18 -0
- package/dist/runtime/built-in-block-non-ts-artifacts.js +563 -0
- package/dist/runtime/cli-doctor.js +10 -5
- package/dist/runtime/template-builtins.d.ts +9 -0
- package/dist/runtime/template-builtins.js +31 -1
- package/dist/runtime/template-source.js +9 -3
- package/package.json +4 -4
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/render.php.mustache +0 -152
- package/templates/basic/src/editor.scss.mustache +0 -8
- package/templates/basic/src/hooks.ts.mustache +0 -18
- package/templates/basic/src/render.php.mustache +0 -19
- package/templates/basic/src/style.scss.mustache +0 -40
- package/templates/compound/src/blocks/{{slugKebabCase}}/style.scss.mustache +0 -31
- package/templates/interactivity/src/editor.scss.mustache +0 -8
- package/templates/interactivity/src/style.scss.mustache +0 -60
- package/templates/persistence/src/render.php.mustache +0 -120
- package/templates/persistence/src/style.scss.mustache +0 -46
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { renderMustacheTemplateString } from "./template-render.js";
|
|
2
|
+
const BASIC_STYLE_TEMPLATE = `/**
|
|
3
|
+
* {{title}} Block Styles
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.{{cssClassName}} {
|
|
7
|
+
padding: 20px;
|
|
8
|
+
border: 1px solid #ddd;
|
|
9
|
+
border-radius: 4px;
|
|
10
|
+
background-color: #fff;
|
|
11
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
12
|
+
|
|
13
|
+
&.is-hidden {
|
|
14
|
+
display: none;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
&__content {
|
|
18
|
+
font-size: 16px;
|
|
19
|
+
line-height: 1.6;
|
|
20
|
+
color: #333;
|
|
21
|
+
|
|
22
|
+
// Alignment styles
|
|
23
|
+
&[data-align="center"] {
|
|
24
|
+
text-align: center;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&[data-align="right"] {
|
|
28
|
+
text-align: right;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&[data-align="justify"] {
|
|
32
|
+
text-align: justify;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Hover state
|
|
37
|
+
&:hover {
|
|
38
|
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
|
39
|
+
transition: box-shadow 0.2s ease;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
const BASIC_EDITOR_STYLE_TEMPLATE = `/**
|
|
44
|
+
* {{title}} Block Editor Styles
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
.{{cssClassName}} {
|
|
48
|
+
outline: 1px dashed #ddd;
|
|
49
|
+
outline-offset: -1px;
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
const BASIC_RENDER_TEMPLATE = `<?php
|
|
53
|
+
/**
|
|
54
|
+
* Optional server render placeholder for {{title}}.
|
|
55
|
+
*
|
|
56
|
+
* The basic scaffold stays static by default. Keep \`src/save.tsx\` as the
|
|
57
|
+
* canonical frontend output unless you intentionally convert this block into a
|
|
58
|
+
* dynamic render path and add \`render\` to \`block.json\`.
|
|
59
|
+
*
|
|
60
|
+
* @package {{slug}}
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
64
|
+
exit;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
?>
|
|
68
|
+
<div class="{{cssClassName}}__server-placeholder" hidden>
|
|
69
|
+
<?php esc_html_e( 'Server render placeholder.', '{{textDomain}}' ); ?>
|
|
70
|
+
</div>
|
|
71
|
+
`;
|
|
72
|
+
const INTERACTIVITY_STYLE_TEMPLATE = `.{{cssClassName}} {
|
|
73
|
+
position: relative;
|
|
74
|
+
padding: 1rem;
|
|
75
|
+
border: 1px solid #dcdcde;
|
|
76
|
+
border-radius: 0.75rem;
|
|
77
|
+
background: #fff;
|
|
78
|
+
|
|
79
|
+
&__content {
|
|
80
|
+
display: grid;
|
|
81
|
+
gap: 0.75rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
&__counter {
|
|
85
|
+
display: inline-flex;
|
|
86
|
+
gap: 0.35rem;
|
|
87
|
+
align-items: center;
|
|
88
|
+
font-size: 0.9rem;
|
|
89
|
+
font-weight: 600;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
&__progress {
|
|
93
|
+
width: 100%;
|
|
94
|
+
height: 0.5rem;
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
background: #f0f0f1;
|
|
97
|
+
border-radius: 999px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
&__progress-bar {
|
|
101
|
+
height: 100%;
|
|
102
|
+
background: #3858e9;
|
|
103
|
+
transition: width 0.2s ease;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
&__animation {
|
|
107
|
+
min-height: 1.5rem;
|
|
108
|
+
font-size: 0.85rem;
|
|
109
|
+
color: #3858e9;
|
|
110
|
+
opacity: 0;
|
|
111
|
+
transition: opacity 0.2s ease;
|
|
112
|
+
|
|
113
|
+
&.is-active {
|
|
114
|
+
opacity: 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
&__completion {
|
|
119
|
+
font-weight: 700;
|
|
120
|
+
color: #06752d;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
&__reset {
|
|
124
|
+
align-self: start;
|
|
125
|
+
padding: 0.4rem 0.7rem;
|
|
126
|
+
border: 1px solid #dcdcde;
|
|
127
|
+
border-radius: 999px;
|
|
128
|
+
background: transparent;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
const INTERACTIVITY_EDITOR_STYLE_TEMPLATE = `/**
|
|
134
|
+
* {{title}} Block Editor Styles
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
.{{cssClassName}} {
|
|
138
|
+
outline: 1px dashed #ddd;
|
|
139
|
+
outline-offset: -1px;
|
|
140
|
+
}
|
|
141
|
+
`;
|
|
142
|
+
const PERSISTENCE_STYLE_TEMPLATE = `.{{cssClassName}} {
|
|
143
|
+
border: 1px solid #dcdcde;
|
|
144
|
+
border-radius: 12px;
|
|
145
|
+
padding: 16px;
|
|
146
|
+
background: #fff;
|
|
147
|
+
|
|
148
|
+
&__meta {
|
|
149
|
+
color: #50575e;
|
|
150
|
+
font-size: 13px;
|
|
151
|
+
margin: 8px 0 0;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.{{frontendCssClassName}} {
|
|
156
|
+
display: grid;
|
|
157
|
+
gap: 12px;
|
|
158
|
+
border: 1px solid #dcdcde;
|
|
159
|
+
border-radius: 12px;
|
|
160
|
+
padding: 16px;
|
|
161
|
+
background: #f6f7f7;
|
|
162
|
+
|
|
163
|
+
&__count {
|
|
164
|
+
display: inline-flex;
|
|
165
|
+
min-width: 3rem;
|
|
166
|
+
justify-content: center;
|
|
167
|
+
font-weight: 700;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
&__notice,
|
|
171
|
+
&__error {
|
|
172
|
+
margin: 0;
|
|
173
|
+
font-size: 13px;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
&__notice {
|
|
177
|
+
color: #50575e;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
&__error {
|
|
181
|
+
color: #b32d2e;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
button {
|
|
185
|
+
width: fit-content;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
`;
|
|
189
|
+
const PERSISTENCE_RENDER_TEMPLATE = `<?php
|
|
190
|
+
/**
|
|
191
|
+
* Dynamic render entry for the {{title}} block.
|
|
192
|
+
*
|
|
193
|
+
* @package {{pascalCase}}
|
|
194
|
+
*/
|
|
195
|
+
|
|
196
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
197
|
+
exit;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
$validator_path = __DIR__ . '/typia-validator.php';
|
|
201
|
+
if ( ! file_exists( $validator_path ) ) {
|
|
202
|
+
return '';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
$validator = require $validator_path;
|
|
206
|
+
if ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {
|
|
207
|
+
return '';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
$normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );
|
|
211
|
+
$validation = $validator->validate( $normalized );
|
|
212
|
+
$resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';
|
|
213
|
+
|
|
214
|
+
if ( empty( $validation['valid'] ) || '' === $resource_key ) {
|
|
215
|
+
return '';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
$alignment = isset( $normalized['alignment'] ) ? (string) $normalized['alignment'] : 'left';
|
|
219
|
+
$button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';
|
|
220
|
+
$content = isset( $normalized['content'] ) ? (string) $normalized['content'] : '';
|
|
221
|
+
$post_id = is_object( $block ) && isset( $block->context['postId'] )
|
|
222
|
+
? (int) $block->context['postId']
|
|
223
|
+
: (int) get_queried_object_id();
|
|
224
|
+
$storage_mode = '{{dataStorageMode}}';
|
|
225
|
+
$persistence_policy = '{{persistencePolicy}}';
|
|
226
|
+
|
|
227
|
+
{{phpPrefix}}_record_rendered_block_instance(
|
|
228
|
+
(int) $post_id,
|
|
229
|
+
'{{namespace}}/{{slugKebabCase}}',
|
|
230
|
+
$resource_key
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
$notice_message = 'authenticated' === $persistence_policy
|
|
234
|
+
? __( 'Sign in to persist this counter.', '{{textDomain}}' )
|
|
235
|
+
: __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
|
|
236
|
+
$context = array(
|
|
237
|
+
'bootstrapReady' => false,
|
|
238
|
+
'buttonLabel' => $button_label,
|
|
239
|
+
'canWrite' => false,
|
|
240
|
+
'client' => array(
|
|
241
|
+
'writeExpiry' => 0,
|
|
242
|
+
'writeNonce' => '',
|
|
243
|
+
'writeToken' => '',
|
|
244
|
+
),
|
|
245
|
+
'count' => 0,
|
|
246
|
+
'error' => '',
|
|
247
|
+
'isBootstrapping' => false,
|
|
248
|
+
'isLoading' => false,
|
|
249
|
+
'isSaving' => false,
|
|
250
|
+
'isVisible' => ! empty( $normalized['isVisible'] ),
|
|
251
|
+
'persistencePolicy' => $persistence_policy,
|
|
252
|
+
'postId' => (int) $post_id,
|
|
253
|
+
'resourceKey' => $resource_key,
|
|
254
|
+
'storage' => $storage_mode,
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
$wrapper_attributes = get_block_wrapper_attributes(
|
|
258
|
+
array(
|
|
259
|
+
'data-wp-context' => wp_json_encode( $context ),
|
|
260
|
+
'data-wp-interactive' => '{{slugKebabCase}}',
|
|
261
|
+
'data-wp-init' => 'callbacks.init',
|
|
262
|
+
'data-wp-run--mounted' => 'callbacks.mounted',
|
|
263
|
+
)
|
|
264
|
+
);
|
|
265
|
+
?>
|
|
266
|
+
|
|
267
|
+
<div <?php echo $wrapper_attributes; ?>>
|
|
268
|
+
<div class="{{frontendCssClassName}}">
|
|
269
|
+
<p class="{{frontendCssClassName}}__content" style="<?php echo esc_attr( 'text-align:' . $alignment ); ?>">
|
|
270
|
+
<?php echo esc_html( $content ); ?>
|
|
271
|
+
</p>
|
|
272
|
+
<p
|
|
273
|
+
class="{{frontendCssClassName}}__notice"
|
|
274
|
+
data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
|
|
275
|
+
hidden
|
|
276
|
+
>
|
|
277
|
+
<?php echo esc_html( $notice_message ); ?>
|
|
278
|
+
</p>
|
|
279
|
+
<p
|
|
280
|
+
class="{{frontendCssClassName}}__error"
|
|
281
|
+
role="status"
|
|
282
|
+
aria-live="polite"
|
|
283
|
+
aria-atomic="true"
|
|
284
|
+
data-wp-bind--hidden="!context.error"
|
|
285
|
+
data-wp-text="context.error"
|
|
286
|
+
hidden
|
|
287
|
+
></p>
|
|
288
|
+
<?php if ( ! empty( $normalized['showCount'] ) ) : ?>
|
|
289
|
+
<span
|
|
290
|
+
class="{{frontendCssClassName}}__count"
|
|
291
|
+
role="status"
|
|
292
|
+
aria-live="polite"
|
|
293
|
+
aria-atomic="true"
|
|
294
|
+
data-wp-text="context.count"
|
|
295
|
+
>
|
|
296
|
+
0
|
|
297
|
+
</span>
|
|
298
|
+
<?php endif; ?>
|
|
299
|
+
<button
|
|
300
|
+
type="button"
|
|
301
|
+
disabled
|
|
302
|
+
data-wp-bind--disabled="!context.canWrite"
|
|
303
|
+
data-wp-on--click="actions.increment"
|
|
304
|
+
>
|
|
305
|
+
<?php echo esc_html( $button_label ); ?>
|
|
306
|
+
</button>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
`;
|
|
310
|
+
const COMPOUND_STYLE_TEMPLATE = `.{{cssClassName}} {
|
|
311
|
+
border: 1px solid #dcdcde;
|
|
312
|
+
border-radius: 12px;
|
|
313
|
+
padding: 1.25rem;
|
|
314
|
+
background: #fff;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.{{cssClassName}}__heading {
|
|
318
|
+
margin: 0 0 0.5rem;
|
|
319
|
+
font-size: 1.2rem;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.{{cssClassName}}__intro {
|
|
323
|
+
margin: 0 0 1rem;
|
|
324
|
+
color: #50575e;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.{{cssClassName}}__items {
|
|
328
|
+
display: grid;
|
|
329
|
+
gap: 0.75rem;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.{{cssClassName}}[data-show-dividers='true'] .{{compoundChildCssClassName}} {
|
|
333
|
+
border-top: 1px solid #dcdcde;
|
|
334
|
+
padding-top: 0.75rem;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.{{cssClassName}}[data-show-dividers='true'] .{{compoundChildCssClassName}}:first-child {
|
|
338
|
+
border-top: 0;
|
|
339
|
+
padding-top: 0;
|
|
340
|
+
}
|
|
341
|
+
`;
|
|
342
|
+
function renderArtifact(relativePath, template, view) {
|
|
343
|
+
const source = renderMustacheTemplateString(template, view);
|
|
344
|
+
return {
|
|
345
|
+
relativePath,
|
|
346
|
+
source: source.endsWith("\n") ? source : `${source}\n`,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function toPhpSingleQuotedString(value) {
|
|
350
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
351
|
+
}
|
|
352
|
+
function buildBasicArtifacts(variables) {
|
|
353
|
+
return [
|
|
354
|
+
renderArtifact("src/editor.scss", BASIC_EDITOR_STYLE_TEMPLATE, variables),
|
|
355
|
+
renderArtifact("src/style.scss", BASIC_STYLE_TEMPLATE, variables),
|
|
356
|
+
renderArtifact("src/render.php", BASIC_RENDER_TEMPLATE, variables),
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
function buildInteractivityArtifacts(variables) {
|
|
360
|
+
return [
|
|
361
|
+
renderArtifact("src/editor.scss", INTERACTIVITY_EDITOR_STYLE_TEMPLATE, variables),
|
|
362
|
+
renderArtifact("src/style.scss", INTERACTIVITY_STYLE_TEMPLATE, variables),
|
|
363
|
+
];
|
|
364
|
+
}
|
|
365
|
+
function buildPersistenceArtifacts(variables) {
|
|
366
|
+
return [
|
|
367
|
+
renderArtifact("src/style.scss", PERSISTENCE_STYLE_TEMPLATE, variables),
|
|
368
|
+
renderArtifact("src/render.php", PERSISTENCE_RENDER_TEMPLATE, variables),
|
|
369
|
+
];
|
|
370
|
+
}
|
|
371
|
+
function buildCompoundArtifacts(variables) {
|
|
372
|
+
const artifacts = [
|
|
373
|
+
renderArtifact(`src/blocks/${variables.slugKebabCase}/style.scss`, COMPOUND_STYLE_TEMPLATE, variables),
|
|
374
|
+
];
|
|
375
|
+
if (variables.compoundPersistenceEnabled === "true") {
|
|
376
|
+
const renderView = {
|
|
377
|
+
...variables,
|
|
378
|
+
titlePhpLiteral: toPhpSingleQuotedString(variables.title),
|
|
379
|
+
};
|
|
380
|
+
const renderSource = `<?php
|
|
381
|
+
/**
|
|
382
|
+
* Dynamic render entry for the {{title}} compound parent block.
|
|
383
|
+
*
|
|
384
|
+
* @package {{pascalCase}}
|
|
385
|
+
*/
|
|
386
|
+
|
|
387
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
388
|
+
exit;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
$validator_path = __DIR__ . '/typia-validator.php';
|
|
392
|
+
if ( ! file_exists( $validator_path ) ) {
|
|
393
|
+
return '';
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
$validator = require $validator_path;
|
|
397
|
+
if ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {
|
|
398
|
+
return '';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
$normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );
|
|
402
|
+
$validation = $validator->validate( $normalized );
|
|
403
|
+
$resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';
|
|
404
|
+
$heading = isset( $normalized['heading'] ) ? (string) $normalized['heading'] : {{titlePhpLiteral}};
|
|
405
|
+
$intro = isset( $normalized['intro'] ) ? (string) $normalized['intro'] : '';
|
|
406
|
+
$button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';
|
|
407
|
+
$show_count = ! empty( $normalized['showCount'] );
|
|
408
|
+
$show_dividers = ! empty( $normalized['showDividers'] );
|
|
409
|
+
$post_id = is_object( $block ) && isset( $block->context['postId'] )
|
|
410
|
+
? (int) $block->context['postId']
|
|
411
|
+
: (int) get_queried_object_id();
|
|
412
|
+
$storage_mode = '{{dataStorageMode}}';
|
|
413
|
+
$persistence_policy = '{{persistencePolicy}}';
|
|
414
|
+
|
|
415
|
+
{{phpPrefix}}_record_rendered_block_instance(
|
|
416
|
+
(int) $post_id,
|
|
417
|
+
'{{namespace}}/{{slugKebabCase}}',
|
|
418
|
+
$resource_key
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
$notice_message = 'authenticated' === $persistence_policy
|
|
422
|
+
? __( 'Sign in to persist this counter.', '{{textDomain}}' )
|
|
423
|
+
: __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
|
|
424
|
+
|
|
425
|
+
if ( empty( $validation['valid'] ) || '' === $resource_key ) {
|
|
426
|
+
return '';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
$context = array(
|
|
430
|
+
'bootstrapReady' => false,
|
|
431
|
+
'buttonLabel' => $button_label,
|
|
432
|
+
'canWrite' => false,
|
|
433
|
+
'client' => array(
|
|
434
|
+
'writeExpiry' => 0,
|
|
435
|
+
'writeNonce' => '',
|
|
436
|
+
'writeToken' => '',
|
|
437
|
+
),
|
|
438
|
+
'count' => 0,
|
|
439
|
+
'error' => '',
|
|
440
|
+
'isBootstrapping' => false,
|
|
441
|
+
'isLoading' => false,
|
|
442
|
+
'isSaving' => false,
|
|
443
|
+
'persistencePolicy' => $persistence_policy,
|
|
444
|
+
'postId' => (int) $post_id,
|
|
445
|
+
'resourceKey' => $resource_key,
|
|
446
|
+
'showCount' => $show_count,
|
|
447
|
+
'storage' => $storage_mode,
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
$allowed_inner_html = wp_kses_allowed_html( 'post' );
|
|
451
|
+
|
|
452
|
+
foreach ( $allowed_inner_html as &$allowed_attributes ) {
|
|
453
|
+
if ( ! is_array( $allowed_attributes ) ) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
$allowed_attributes['data-wp-bind--disabled'] = true;
|
|
458
|
+
$allowed_attributes['data-wp-bind--hidden'] = true;
|
|
459
|
+
$allowed_attributes['data-wp-bind--value'] = true;
|
|
460
|
+
$allowed_attributes['data-wp-class'] = true;
|
|
461
|
+
$allowed_attributes['data-wp-class--active'] = true;
|
|
462
|
+
$allowed_attributes['data-wp-context'] = true;
|
|
463
|
+
$allowed_attributes['data-wp-init'] = true;
|
|
464
|
+
$allowed_attributes['data-wp-interactive'] = true;
|
|
465
|
+
$allowed_attributes['data-wp-on--click'] = true;
|
|
466
|
+
$allowed_attributes['data-wp-on--mouseenter'] = true;
|
|
467
|
+
$allowed_attributes['data-wp-on--mouseleave'] = true;
|
|
468
|
+
$allowed_attributes['data-wp-run--mounted'] = true;
|
|
469
|
+
$allowed_attributes['data-wp-style--width'] = true;
|
|
470
|
+
$allowed_attributes['data-wp-text'] = true;
|
|
471
|
+
}
|
|
472
|
+
unset( $allowed_attributes );
|
|
473
|
+
|
|
474
|
+
$sanitized_content = wp_kses( $content, $allowed_inner_html );
|
|
475
|
+
|
|
476
|
+
$wrapper_attributes = get_block_wrapper_attributes(
|
|
477
|
+
array(
|
|
478
|
+
'class' => '{{cssClassName}}',
|
|
479
|
+
'data-show-dividers' => $show_dividers ? 'true' : 'false',
|
|
480
|
+
'data-wp-context' => wp_json_encode( $context ),
|
|
481
|
+
'data-wp-init' => 'callbacks.init',
|
|
482
|
+
'data-wp-interactive' => '{{slugKebabCase}}',
|
|
483
|
+
'data-wp-run--mounted' => 'callbacks.mounted',
|
|
484
|
+
)
|
|
485
|
+
);
|
|
486
|
+
?>
|
|
487
|
+
|
|
488
|
+
<div <?php echo $wrapper_attributes; ?>>
|
|
489
|
+
<h3 class="{{cssClassName}}__heading"><?php echo esc_html( $heading ); ?></h3>
|
|
490
|
+
<?php if ( '' !== $intro ) : ?>
|
|
491
|
+
<p class="{{cssClassName}}__intro"><?php echo esc_html( $intro ); ?></p>
|
|
492
|
+
<?php endif; ?>
|
|
493
|
+
<p
|
|
494
|
+
class="{{cssClassName}}__notice"
|
|
495
|
+
data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
|
|
496
|
+
hidden
|
|
497
|
+
>
|
|
498
|
+
<?php echo esc_html( $notice_message ); ?>
|
|
499
|
+
</p>
|
|
500
|
+
<p
|
|
501
|
+
class="{{cssClassName}}__error"
|
|
502
|
+
role="status"
|
|
503
|
+
aria-live="polite"
|
|
504
|
+
aria-atomic="true"
|
|
505
|
+
data-wp-bind--hidden="!context.error"
|
|
506
|
+
data-wp-text="context.error"
|
|
507
|
+
hidden
|
|
508
|
+
></p>
|
|
509
|
+
<?php if ( $show_count ) : ?>
|
|
510
|
+
<div class="{{cssClassName}}__counter">
|
|
511
|
+
<span
|
|
512
|
+
class="{{cssClassName}}__count"
|
|
513
|
+
role="status"
|
|
514
|
+
aria-live="polite"
|
|
515
|
+
aria-atomic="true"
|
|
516
|
+
data-wp-text="context.count"
|
|
517
|
+
>0</span>
|
|
518
|
+
<button
|
|
519
|
+
type="button"
|
|
520
|
+
disabled
|
|
521
|
+
data-wp-bind--disabled="!context.canWrite"
|
|
522
|
+
data-wp-on--click="actions.increment"
|
|
523
|
+
>
|
|
524
|
+
<?php echo esc_html( $button_label ); ?>
|
|
525
|
+
</button>
|
|
526
|
+
</div>
|
|
527
|
+
<?php endif; ?>
|
|
528
|
+
<div class="{{cssClassName}}__items">
|
|
529
|
+
<?php echo $sanitized_content; ?>
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
`;
|
|
533
|
+
artifacts.push(renderArtifact(`src/blocks/${variables.slugKebabCase}/render.php`, renderSource, renderView));
|
|
534
|
+
}
|
|
535
|
+
return artifacts;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Builds non-TypeScript scaffold artifacts for built-in block templates.
|
|
539
|
+
*
|
|
540
|
+
* @param options Build options for the selected built-in template family.
|
|
541
|
+
* @param options.templateId Built-in template identifier that controls which
|
|
542
|
+
* non-TS files should be emitted.
|
|
543
|
+
* @param options.variables Scaffold template variables used to render the
|
|
544
|
+
* generated sources.
|
|
545
|
+
* @returns An array of emitter-owned SCSS and PHP artifacts for the selected
|
|
546
|
+
* built-in template family.
|
|
547
|
+
*/
|
|
548
|
+
export function buildBuiltInNonTsArtifacts({ templateId, variables, }) {
|
|
549
|
+
switch (templateId) {
|
|
550
|
+
case "basic":
|
|
551
|
+
return buildBasicArtifacts(variables);
|
|
552
|
+
case "interactivity":
|
|
553
|
+
return buildInteractivityArtifacts(variables);
|
|
554
|
+
case "persistence":
|
|
555
|
+
return buildPersistenceArtifacts(variables);
|
|
556
|
+
case "compound":
|
|
557
|
+
return buildCompoundArtifacts(variables);
|
|
558
|
+
default: {
|
|
559
|
+
const unhandledTemplateId = templateId;
|
|
560
|
+
throw new Error(`Unhandled built-in template id: ${unhandledTemplateId}`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
@@ -3,7 +3,7 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
5
|
import { access, constants as fsConstants, rm, writeFile } from "node:fs/promises";
|
|
6
|
-
import { getBuiltInTemplateLayerDirs } from "./template-builtins.js";
|
|
6
|
+
import { getBuiltInTemplateLayerDirs, isOmittableBuiltInTemplateLayerDir, } from "./template-builtins.js";
|
|
7
7
|
import { HOOKED_BLOCK_ANCHOR_PATTERN, HOOKED_BLOCK_POSITION_SET, } from "./hooked-blocks.js";
|
|
8
8
|
import { isBuiltInTemplateId, listTemplates } from "./template-registry.js";
|
|
9
9
|
import { readWorkspaceInventory } from "./workspace-inventory.js";
|
|
@@ -319,13 +319,18 @@ export async function getDoctorChecks(cwd) {
|
|
|
319
319
|
}),
|
|
320
320
|
]))
|
|
321
321
|
: getBuiltInTemplateLayerDirs(builtInTemplateId);
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
322
|
+
const missingRequiredLayer = layerDirs.some((layerDir) => !fs.existsSync(layerDir) &&
|
|
323
|
+
!isOmittableBuiltInTemplateLayerDir(builtInTemplateId, layerDir));
|
|
324
|
+
const existingLayerDirs = layerDirs.filter((layerDir) => fs.existsSync(layerDir));
|
|
325
|
+
const hasAssets = !missingRequiredLayer &&
|
|
326
|
+
existingLayerDirs.some((layerDir) => fs.existsSync(path.join(layerDir, "package.json.mustache"))) &&
|
|
327
|
+
existingLayerDirs.some((layerDir) => fs.existsSync(path.join(layerDir, "src")));
|
|
325
328
|
checks.push({
|
|
326
329
|
status: hasAssets ? "pass" : "fail",
|
|
327
330
|
label: `Template ${template.id}`,
|
|
328
|
-
detail: hasAssets
|
|
331
|
+
detail: hasAssets
|
|
332
|
+
? existingLayerDirs.join(" + ")
|
|
333
|
+
: "Missing core template assets",
|
|
329
334
|
});
|
|
330
335
|
}
|
|
331
336
|
let workspace = null;
|
|
@@ -27,6 +27,15 @@ export interface MaterializedBuiltInTemplateSource {
|
|
|
27
27
|
* resolve to the shared base plus their own template directory.
|
|
28
28
|
*/
|
|
29
29
|
export declare function getBuiltInTemplateLayerDirs(templateId: BuiltInTemplateId, { persistenceEnabled, persistencePolicy, }?: BuiltInTemplateVariantOptions): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Returns whether a missing built-in overlay directory is expected because the
|
|
32
|
+
* template family no longer ships any Mustache assets in that layer.
|
|
33
|
+
*
|
|
34
|
+
* @param templateId Built-in template family being resolved.
|
|
35
|
+
* @param layerDir Candidate overlay directory for that family.
|
|
36
|
+
* @returns True when the missing layer can be skipped safely.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isOmittableBuiltInTemplateLayerDir(templateId: BuiltInTemplateId, layerDir: string): boolean;
|
|
30
39
|
/**
|
|
31
40
|
* Materializes a built-in template into a temporary directory by copying each
|
|
32
41
|
* resolved layer in order.
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { promises as fsp } from "node:fs";
|
|
4
5
|
import { getTemplateById, SHARED_BASE_TEMPLATE_ROOT, SHARED_COMPOUND_TEMPLATE_ROOT, SHARED_PERSISTENCE_TEMPLATE_ROOT, SHARED_REST_HELPER_TEMPLATE_ROOT, } from "./template-registry.js";
|
|
6
|
+
const OMITTABLE_BUILT_IN_OVERLAY_TEMPLATE_IDS = new Set([
|
|
7
|
+
"basic",
|
|
8
|
+
"persistence",
|
|
9
|
+
"compound",
|
|
10
|
+
]);
|
|
5
11
|
/**
|
|
6
12
|
* Returns the ordered overlay directories for a built-in template.
|
|
7
13
|
*
|
|
@@ -33,6 +39,29 @@ export function getBuiltInTemplateLayerDirs(templateId, { persistenceEnabled = f
|
|
|
33
39
|
}
|
|
34
40
|
return [SHARED_BASE_TEMPLATE_ROOT, getTemplateById(templateId).templateDir];
|
|
35
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns whether a missing built-in overlay directory is expected because the
|
|
44
|
+
* template family no longer ships any Mustache assets in that layer.
|
|
45
|
+
*
|
|
46
|
+
* @param templateId Built-in template family being resolved.
|
|
47
|
+
* @param layerDir Candidate overlay directory for that family.
|
|
48
|
+
* @returns True when the missing layer can be skipped safely.
|
|
49
|
+
*/
|
|
50
|
+
export function isOmittableBuiltInTemplateLayerDir(templateId, layerDir) {
|
|
51
|
+
return (OMITTABLE_BUILT_IN_OVERLAY_TEMPLATE_IDS.has(templateId) &&
|
|
52
|
+
layerDir === getTemplateById(templateId).templateDir);
|
|
53
|
+
}
|
|
54
|
+
function resolveMaterializedBuiltInTemplateLayerDirs(templateId, options) {
|
|
55
|
+
return getBuiltInTemplateLayerDirs(templateId, options).flatMap((layerDir) => {
|
|
56
|
+
if (fs.existsSync(layerDir)) {
|
|
57
|
+
return [layerDir];
|
|
58
|
+
}
|
|
59
|
+
if (isOmittableBuiltInTemplateLayerDir(templateId, layerDir)) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`Built-in template layer is missing: ${layerDir}`);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
36
65
|
/**
|
|
37
66
|
* Materializes a built-in template into a temporary directory by copying each
|
|
38
67
|
* resolved layer in order.
|
|
@@ -43,11 +72,12 @@ export function getBuiltInTemplateLayerDirs(templateId, { persistenceEnabled = f
|
|
|
43
72
|
*/
|
|
44
73
|
export async function resolveBuiltInTemplateSource(templateId, options = {}) {
|
|
45
74
|
const template = getTemplateById(templateId);
|
|
75
|
+
const layerDirs = resolveMaterializedBuiltInTemplateLayerDirs(templateId, options);
|
|
46
76
|
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-template-"));
|
|
47
77
|
const templateDir = path.join(tempRoot, templateId);
|
|
48
78
|
try {
|
|
49
79
|
await fsp.mkdir(templateDir, { recursive: true });
|
|
50
|
-
for (const layerDir of
|
|
80
|
+
for (const layerDir of layerDirs) {
|
|
51
81
|
await fsp.cp(layerDir, templateDir, {
|
|
52
82
|
recursive: true,
|
|
53
83
|
force: true,
|
|
@@ -9,10 +9,10 @@ import { pathToFileURL } from "node:url";
|
|
|
9
9
|
import npa from "npm-package-arg";
|
|
10
10
|
import semver from "semver";
|
|
11
11
|
import { x as extractTarball } from "tar";
|
|
12
|
-
import { BUILTIN_TEMPLATE_IDS, PROJECT_TOOLS_PACKAGE_ROOT,
|
|
12
|
+
import { BUILTIN_TEMPLATE_IDS, PROJECT_TOOLS_PACKAGE_ROOT, isBuiltInTemplateId, } from "./template-registry.js";
|
|
13
13
|
import { isPlainObject } from "./object-utils.js";
|
|
14
14
|
import { getRemovedBuiltInTemplateMessage, isRemovedBuiltInTemplateId, } from "./template-defaults.js";
|
|
15
|
-
import { resolveBuiltInTemplateSource } from "./template-builtins.js";
|
|
15
|
+
import { getBuiltInTemplateLayerDirs, isOmittableBuiltInTemplateLayerDir, resolveBuiltInTemplateSource, } from "./template-builtins.js";
|
|
16
16
|
import { getPackageVersions } from "./package-versions.js";
|
|
17
17
|
import { toSegmentPascalCase } from "./string-case.js";
|
|
18
18
|
import { copyRawDirectory, copyRenderedDirectory } from "./template-render.js";
|
|
@@ -522,7 +522,13 @@ async function normalizeCreateBlockSubset(seed, context) {
|
|
|
522
522
|
const blockJson = readRemoteBlockJson(seed.blockDir);
|
|
523
523
|
const sourceRoot = getSeedSourceRoot(seed.blockDir);
|
|
524
524
|
await fsp.mkdir(templateDir, { recursive: true });
|
|
525
|
-
for (const layerDir of
|
|
525
|
+
for (const layerDir of getBuiltInTemplateLayerDirs("basic")) {
|
|
526
|
+
if (!fs.existsSync(layerDir)) {
|
|
527
|
+
if (isOmittableBuiltInTemplateLayerDir("basic", layerDir)) {
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
throw new Error(`Built-in template layer is missing: ${layerDir}`);
|
|
531
|
+
}
|
|
526
532
|
await fsp.cp(layerDir, templateDir, {
|
|
527
533
|
recursive: true,
|
|
528
534
|
force: true,
|