@wp-typia/project-tools 0.16.10 → 0.16.12
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 +9 -3
- package/dist/runtime/built-in-block-artifact-documents.d.ts +3 -0
- package/dist/runtime/built-in-block-artifact-documents.js +2 -0
- package/dist/runtime/built-in-block-artifact-types.d.ts +51 -0
- package/dist/runtime/built-in-block-artifact-types.js +304 -0
- package/dist/runtime/built-in-block-artifacts.js +4 -803
- package/dist/runtime/built-in-block-attribute-emitters.d.ts +71 -0
- package/dist/runtime/built-in-block-attribute-emitters.js +176 -0
- package/dist/runtime/built-in-block-attribute-specs.d.ts +38 -0
- package/dist/runtime/built-in-block-attribute-specs.js +358 -0
- package/dist/runtime/built-in-block-code-templates/basic.d.ts +4 -0
- package/dist/runtime/built-in-block-code-templates/basic.js +249 -0
- package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +4 -0
- package/dist/runtime/built-in-block-code-templates/compound-child.js +138 -0
- package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +6 -0
- package/dist/runtime/built-in-block-code-templates/compound-parent.js +227 -0
- package/dist/runtime/built-in-block-code-templates/compound-persistence.d.ts +4 -0
- package/dist/runtime/built-in-block-code-templates/compound-persistence.js +478 -0
- package/dist/runtime/built-in-block-code-templates/compound.d.ts +3 -0
- package/dist/runtime/built-in-block-code-templates/compound.js +3 -0
- package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +5 -0
- package/dist/runtime/built-in-block-code-templates/interactivity.js +547 -0
- package/dist/runtime/built-in-block-code-templates/persistence.d.ts +5 -0
- package/dist/runtime/built-in-block-code-templates/persistence.js +550 -0
- package/dist/runtime/built-in-block-code-templates/shared.d.ts +16 -0
- package/dist/runtime/built-in-block-code-templates/shared.js +53 -0
- package/dist/runtime/built-in-block-code-templates.d.ts +5 -32
- package/dist/runtime/built-in-block-code-templates.js +5 -2230
- package/dist/runtime/cli-add-block-config.d.ts +6 -0
- package/dist/runtime/cli-add-block-config.js +143 -0
- package/dist/runtime/cli-add-block-legacy-validator.d.ts +4 -0
- package/dist/runtime/cli-add-block-legacy-validator.js +168 -0
- package/dist/runtime/cli-add-block.js +3 -301
- package/dist/runtime/cli-add-workspace-assets.d.ts +38 -0
- package/dist/runtime/cli-add-workspace-assets.js +399 -0
- package/dist/runtime/cli-add-workspace.d.ts +2 -38
- package/dist/runtime/cli-add-workspace.js +5 -396
- package/dist/runtime/cli-doctor-environment.d.ts +12 -0
- package/dist/runtime/cli-doctor-environment.js +123 -0
- package/dist/runtime/cli-doctor-workspace.d.ts +14 -0
- package/dist/runtime/cli-doctor-workspace.js +296 -0
- package/dist/runtime/cli-doctor.d.ts +4 -2
- package/dist/runtime/cli-doctor.js +10 -405
- package/dist/runtime/cli-help.js +1 -1
- package/dist/runtime/cli-scaffold.js +1 -1
- package/dist/runtime/migration-command-surface.d.ts +67 -0
- package/dist/runtime/migration-command-surface.js +189 -0
- package/dist/runtime/migration-diff-rename.d.ts +13 -0
- package/dist/runtime/migration-diff-rename.js +192 -0
- package/dist/runtime/migration-diff-transform.d.ts +14 -0
- package/dist/runtime/migration-diff-transform.js +105 -0
- package/dist/runtime/migration-diff.js +12 -297
- package/dist/runtime/migration-generated-artifacts.d.ts +3 -0
- package/dist/runtime/migration-generated-artifacts.js +41 -0
- package/dist/runtime/migration-maintenance.d.ts +51 -0
- package/dist/runtime/migration-maintenance.js +380 -0
- package/dist/runtime/migration-planning.d.ts +23 -0
- package/dist/runtime/migration-planning.js +131 -0
- package/dist/runtime/migration-project-config-source.d.ts +6 -0
- package/dist/runtime/migration-project-config-source.js +424 -0
- package/dist/runtime/migration-project-layout-discovery.d.ts +61 -0
- package/dist/runtime/migration-project-layout-discovery.js +337 -0
- package/dist/runtime/migration-project-layout-paths.d.ts +135 -0
- package/dist/runtime/migration-project-layout-paths.js +288 -0
- package/dist/runtime/migration-project-layout.d.ts +3 -0
- package/dist/runtime/migration-project-layout.js +2 -0
- package/dist/runtime/migration-project-workspace.d.ts +47 -0
- package/dist/runtime/migration-project-workspace.js +212 -0
- package/dist/runtime/migration-project.d.ts +4 -94
- package/dist/runtime/migration-project.js +3 -1101
- package/dist/runtime/migration-render-diff-rule.d.ts +5 -0
- package/dist/runtime/migration-render-diff-rule.js +120 -0
- package/dist/runtime/migration-render-execution.d.ts +3 -0
- package/dist/runtime/migration-render-execution.js +428 -0
- package/dist/runtime/migration-render-generated.d.ts +27 -0
- package/dist/runtime/migration-render-generated.js +230 -0
- package/dist/runtime/migration-render-support.d.ts +3 -0
- package/dist/runtime/migration-render-support.js +16 -0
- package/dist/runtime/migration-render.d.ts +3 -33
- package/dist/runtime/migration-render.js +3 -789
- package/dist/runtime/migration-ui-capability.js +1 -1
- package/dist/runtime/migrations.d.ts +24 -118
- package/dist/runtime/migrations.js +12 -700
- package/dist/runtime/scaffold-bootstrap.d.ts +45 -0
- package/dist/runtime/scaffold-bootstrap.js +185 -0
- package/dist/runtime/scaffold-package-manager-files.d.ts +35 -0
- package/dist/runtime/scaffold-package-manager-files.js +79 -0
- package/dist/runtime/scaffold.d.ts +1 -12
- package/dist/runtime/scaffold.js +10 -393
- package/dist/runtime/template-source-contracts.d.ts +81 -0
- package/dist/runtime/template-source-contracts.js +1 -0
- package/dist/runtime/template-source-external.d.ts +21 -0
- package/dist/runtime/template-source-external.js +184 -0
- package/dist/runtime/template-source-locators.d.ts +4 -0
- package/dist/runtime/template-source-locators.js +72 -0
- package/dist/runtime/template-source-normalization.d.ts +7 -0
- package/dist/runtime/template-source-normalization.js +53 -0
- package/dist/runtime/template-source-remote.d.ts +23 -0
- package/dist/runtime/template-source-remote.js +336 -0
- package/dist/runtime/template-source-seeds.d.ts +12 -0
- package/dist/runtime/template-source-seeds.js +243 -0
- package/dist/runtime/template-source.d.ts +4 -86
- package/dist/runtime/template-source.js +9 -828
- package/package.json +5 -5
|
@@ -2,2233 +2,8 @@
|
|
|
2
2
|
* Built-in emitter template bodies grouped away from artifact assembly so
|
|
3
3
|
* authoring stays focused on the emitted TS/TSX/PHP sources themselves.
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
formatValidationErrors,
|
|
11
|
-
} from '@wp-typia/block-runtime/validation';
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
formatValidationError,
|
|
15
|
-
formatValidationErrors,
|
|
16
|
-
type TypiaValidationError,
|
|
17
|
-
type ValidationResult,
|
|
18
|
-
type ValidationState,
|
|
19
|
-
} from '@wp-typia/block-runtime/validation';
|
|
20
|
-
|
|
21
|
-
export const useTypiaValidation = createUseTypiaValidationHook( {
|
|
22
|
-
useMemo,
|
|
23
|
-
} );
|
|
24
|
-
`;
|
|
25
|
-
export const BLOCK_METADATA_WRAPPER_TEMPLATE = `import rawMetadata from './block.json';
|
|
26
|
-
import { defineScaffoldBlockMetadata } from '@wp-typia/block-runtime/blocks';
|
|
27
|
-
|
|
28
|
-
const metadata = defineScaffoldBlockMetadata(rawMetadata);
|
|
29
|
-
|
|
30
|
-
export default metadata;
|
|
31
|
-
`;
|
|
32
|
-
export const MANIFEST_DOCUMENT_WRAPPER_TEMPLATE = `import rawCurrentManifest from './typia.manifest.json';
|
|
33
|
-
import { defineManifestDocument } from '@wp-typia/block-runtime/editor';
|
|
34
|
-
|
|
35
|
-
const currentManifest = defineManifestDocument(rawCurrentManifest);
|
|
36
|
-
|
|
37
|
-
export default currentManifest;
|
|
38
|
-
`;
|
|
39
|
-
export const MANIFEST_DEFAULTS_DOCUMENT_WRAPPER_TEMPLATE = `import rawCurrentManifest from './typia.manifest.json';
|
|
40
|
-
import { defineManifestDefaultsDocument } from '@wp-typia/block-runtime/defaults';
|
|
41
|
-
|
|
42
|
-
const currentManifest = defineManifestDefaultsDocument(rawCurrentManifest);
|
|
43
|
-
|
|
44
|
-
export default currentManifest;
|
|
45
|
-
`;
|
|
46
|
-
export const BASIC_EDIT_TEMPLATE = `/**
|
|
47
|
-
* Editor component for {{title}} Block
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';
|
|
51
|
-
import {
|
|
52
|
-
InspectorControls,
|
|
53
|
-
RichText,
|
|
54
|
-
useBlockProps,
|
|
55
|
-
} from '@wordpress/block-editor';
|
|
56
|
-
import { Notice, PanelBody, TextControl } from '@wordpress/components';
|
|
57
|
-
import { __ } from '@wordpress/i18n';
|
|
58
|
-
import currentManifest from './manifest-document';
|
|
59
|
-
import {
|
|
60
|
-
InspectorFromManifest,
|
|
61
|
-
useEditorFields,
|
|
62
|
-
useTypedAttributeUpdater,
|
|
63
|
-
} from '@wp-typia/block-runtime/inspector';
|
|
64
|
-
import { {{pascalCase}}Attributes } from './types';
|
|
65
|
-
import {
|
|
66
|
-
sanitize{{pascalCase}}Attributes,
|
|
67
|
-
validate{{pascalCase}}Attributes,
|
|
68
|
-
} from './validators';
|
|
69
|
-
import { useTypiaValidation } from './hooks';
|
|
70
|
-
|
|
71
|
-
type EditProps = BlockEditProps<{{pascalCase}}Attributes>;
|
|
72
|
-
|
|
73
|
-
const validationErrorItemStyle = { color: '#cc1818', fontSize: '12px' };
|
|
74
|
-
const validationListStyle = { margin: 0, paddingLeft: '1em' };
|
|
75
|
-
|
|
76
|
-
function Edit({ attributes, setAttributes }: EditProps) {
|
|
77
|
-
const isVisible = attributes.isVisible !== false;
|
|
78
|
-
const blockProps = useBlockProps({
|
|
79
|
-
className: \`{{cssClassName}}\${isVisible ? '' : ' is-hidden'}\`,
|
|
80
|
-
});
|
|
81
|
-
const editorFields = useEditorFields(currentManifest, {
|
|
82
|
-
hidden: ['id', 'schemaVersion'],
|
|
83
|
-
manual: ['content'],
|
|
84
|
-
labels: {
|
|
85
|
-
alignment: __('Alignment', '{{textDomain}}'),
|
|
86
|
-
className: __('CSS Class', '{{textDomain}}'),
|
|
87
|
-
content: __('Content', '{{textDomain}}'),
|
|
88
|
-
isVisible: __('Visible', '{{textDomain}}'),
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
const classNameField = editorFields.getField('className');
|
|
92
|
-
const { errorMessages, isValid } = useTypiaValidation(
|
|
93
|
-
attributes,
|
|
94
|
-
validate{{pascalCase}}Attributes
|
|
95
|
-
);
|
|
96
|
-
const validateEditorUpdate = (nextAttributes: {{pascalCase}}Attributes) => {
|
|
97
|
-
try {
|
|
98
|
-
return {
|
|
99
|
-
data: sanitize{{pascalCase}}Attributes(nextAttributes),
|
|
100
|
-
errors: [],
|
|
101
|
-
isValid: true as const,
|
|
102
|
-
};
|
|
103
|
-
} catch {
|
|
104
|
-
return validate{{pascalCase}}Attributes(nextAttributes);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
const { updateField } = useTypedAttributeUpdater(
|
|
108
|
-
attributes,
|
|
109
|
-
setAttributes,
|
|
110
|
-
validateEditorUpdate
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<>
|
|
115
|
-
<InspectorControls>
|
|
116
|
-
<InspectorFromManifest
|
|
117
|
-
attributes={attributes}
|
|
118
|
-
fieldLookup={editorFields}
|
|
119
|
-
onChange={updateField}
|
|
120
|
-
paths={['alignment', 'isVisible']}
|
|
121
|
-
title={__('Settings', '{{textDomain}}')}
|
|
122
|
-
>
|
|
123
|
-
<TextControl
|
|
124
|
-
label={__('Content', '{{textDomain}}')}
|
|
125
|
-
value={attributes.content || ''}
|
|
126
|
-
onChange={(value) => updateField('content', value)}
|
|
127
|
-
help={__('Mirrors the main block content.', '{{textDomain}}')}
|
|
128
|
-
/>
|
|
129
|
-
|
|
130
|
-
<TextControl
|
|
131
|
-
label={classNameField?.label || __('CSS Class', '{{textDomain}}')}
|
|
132
|
-
value={attributes.className || ''}
|
|
133
|
-
onChange={(value) => updateField('className', value)}
|
|
134
|
-
help={__('Add an optional CSS class name.', '{{textDomain}}')}
|
|
135
|
-
/>
|
|
136
|
-
</InspectorFromManifest>
|
|
137
|
-
|
|
138
|
-
{!isValid && (
|
|
139
|
-
<PanelBody title={__('Validation Errors', '{{textDomain}}')} initialOpen>
|
|
140
|
-
{errorMessages.map((error, index) => (
|
|
141
|
-
<div key={index} style={validationErrorItemStyle}>
|
|
142
|
-
• {error}
|
|
143
|
-
</div>
|
|
144
|
-
))}
|
|
145
|
-
</PanelBody>
|
|
146
|
-
)}
|
|
147
|
-
</InspectorControls>
|
|
148
|
-
|
|
149
|
-
<div {...blockProps}>
|
|
150
|
-
<div className="{{cssClassName}}__content">
|
|
151
|
-
<RichText
|
|
152
|
-
tagName="p"
|
|
153
|
-
value={attributes.content || ''}
|
|
154
|
-
onChange={(value) => updateField('content', value)}
|
|
155
|
-
placeholder={__('Add your content...', '{{textDomain}}')}
|
|
156
|
-
/>
|
|
157
|
-
</div>
|
|
158
|
-
{!isValid && (
|
|
159
|
-
<Notice status="error" isDismissible={false}>
|
|
160
|
-
<p>
|
|
161
|
-
<strong>{__('Validation Errors', '{{textDomain}}')}</strong>
|
|
162
|
-
</p>
|
|
163
|
-
<ul style={validationListStyle}>
|
|
164
|
-
{errorMessages.map((error, index) => (
|
|
165
|
-
<li key={index}>{error}</li>
|
|
166
|
-
))}
|
|
167
|
-
</ul>
|
|
168
|
-
</Notice>
|
|
169
|
-
)}
|
|
170
|
-
</div>
|
|
171
|
-
</>
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export default Edit;
|
|
176
|
-
`;
|
|
177
|
-
export const BASIC_SAVE_TEMPLATE = `/**
|
|
178
|
-
* Save/Frontend component for {{title}} Block
|
|
179
|
-
*/
|
|
180
|
-
|
|
181
|
-
import { RichText, useBlockProps } from '@wordpress/block-editor';
|
|
182
|
-
import { {{pascalCase}}Attributes } from './types';
|
|
183
|
-
|
|
184
|
-
interface SaveProps {
|
|
185
|
-
attributes: {{pascalCase}}Attributes;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export default function Save({ attributes }: SaveProps) {
|
|
189
|
-
const isVisible = attributes.isVisible !== false;
|
|
190
|
-
const blockProps = useBlockProps.save({
|
|
191
|
-
className: \`{{cssClassName}}\${isVisible ? '' : ' is-hidden'}\`,
|
|
192
|
-
hidden: isVisible ? undefined : true,
|
|
193
|
-
'aria-hidden': isVisible ? undefined : 'true',
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
return (
|
|
197
|
-
<div {...blockProps}>
|
|
198
|
-
<div
|
|
199
|
-
className="{{cssClassName}}__content"
|
|
200
|
-
data-align={attributes.alignment || 'left'}
|
|
201
|
-
>
|
|
202
|
-
<RichText.Content tagName="p" value={attributes.content} />
|
|
203
|
-
</div>
|
|
204
|
-
</div>
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
`;
|
|
208
|
-
export const BASIC_INDEX_TEMPLATE = `/**
|
|
209
|
-
* WordPress {{title}} Block
|
|
210
|
-
*
|
|
211
|
-
* Typia-powered type-safe block
|
|
212
|
-
*/
|
|
213
|
-
|
|
214
|
-
import {
|
|
215
|
-
registerScaffoldBlockType,
|
|
216
|
-
type BlockConfiguration,
|
|
217
|
-
} from '@wp-typia/block-types/blocks/registration';
|
|
218
|
-
import type { BlockSupports } from '@wp-typia/block-types/blocks/supports';
|
|
219
|
-
import { __ } from '@wordpress/i18n';
|
|
220
|
-
import {
|
|
221
|
-
buildScaffoldBlockRegistration,
|
|
222
|
-
parseScaffoldBlockMetadata,
|
|
223
|
-
} from '@wp-typia/block-runtime/blocks';
|
|
224
|
-
|
|
225
|
-
// Import components
|
|
226
|
-
import Edit from './edit';
|
|
227
|
-
import Save from './save';
|
|
228
|
-
import metadata from './block-metadata';
|
|
229
|
-
import './editor.scss';
|
|
230
|
-
import './style.scss';
|
|
231
|
-
|
|
232
|
-
// Import types
|
|
233
|
-
import { {{pascalCase}}Attributes } from './types';
|
|
234
|
-
import { validators } from './validators';
|
|
235
|
-
|
|
236
|
-
const scaffoldSupports = {
|
|
237
|
-
html: false,
|
|
238
|
-
multiple: true,
|
|
239
|
-
align: ['wide', 'full'],
|
|
240
|
-
} satisfies BlockSupports;
|
|
241
|
-
|
|
242
|
-
// Register the block
|
|
243
|
-
const registration = buildScaffoldBlockRegistration(
|
|
244
|
-
parseScaffoldBlockMetadata<BlockConfiguration<{{pascalCase}}Attributes>>(metadata),
|
|
245
|
-
{
|
|
246
|
-
supports: scaffoldSupports,
|
|
247
|
-
example: {
|
|
248
|
-
attributes: validators.random(),
|
|
249
|
-
},
|
|
250
|
-
edit: Edit,
|
|
251
|
-
save: Save,
|
|
252
|
-
}
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
registerScaffoldBlockType(registration.name, registration.settings);
|
|
256
|
-
`;
|
|
257
|
-
export const BASIC_VALIDATORS_TEMPLATE = `import typia from 'typia';
|
|
258
|
-
import currentManifest from "./manifest-defaults-document";
|
|
259
|
-
import { {{pascalCase}}Attributes, {{pascalCase}}ValidationResult } from "./types";
|
|
260
|
-
import { generateBlockId } from "@wp-typia/block-runtime/identifiers";
|
|
261
|
-
import { createTemplateValidatorToolkit } from "./validator-toolkit";
|
|
262
|
-
|
|
263
|
-
const scaffoldValidators = createTemplateValidatorToolkit<{{pascalCase}}Attributes>({
|
|
264
|
-
assert: typia.createAssert<{{pascalCase}}Attributes>(),
|
|
265
|
-
clone: typia.misc.createClone<{{pascalCase}}Attributes>() as (
|
|
266
|
-
value: {{pascalCase}}Attributes,
|
|
267
|
-
) => {{pascalCase}}Attributes,
|
|
268
|
-
is: typia.createIs<{{pascalCase}}Attributes>(),
|
|
269
|
-
manifest: currentManifest,
|
|
270
|
-
prune: typia.misc.createPrune<{{pascalCase}}Attributes>(),
|
|
271
|
-
random: typia.createRandom<{{pascalCase}}Attributes>() as (
|
|
272
|
-
...args: unknown[]
|
|
273
|
-
) => {{pascalCase}}Attributes,
|
|
274
|
-
finalize: (normalized) => ({
|
|
275
|
-
...normalized,
|
|
276
|
-
id: normalized.id && normalized.id.length > 0 ? normalized.id : generateBlockId(),
|
|
277
|
-
}),
|
|
278
|
-
validate: typia.createValidate<{{pascalCase}}Attributes>(),
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
export const validate{{pascalCase}}Attributes =
|
|
282
|
-
scaffoldValidators.validateAttributes as (
|
|
283
|
-
attributes: unknown,
|
|
284
|
-
) => {{pascalCase}}ValidationResult;
|
|
285
|
-
|
|
286
|
-
export const validators = scaffoldValidators.validators;
|
|
287
|
-
|
|
288
|
-
export const sanitize{{pascalCase}}Attributes =
|
|
289
|
-
scaffoldValidators.sanitizeAttributes as (
|
|
290
|
-
attributes: Partial<{{pascalCase}}Attributes>,
|
|
291
|
-
) => {{pascalCase}}Attributes;
|
|
292
|
-
|
|
293
|
-
export const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;
|
|
294
|
-
`;
|
|
295
|
-
export const INTERACTIVITY_EDIT_TEMPLATE = `import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';
|
|
296
|
-
import { __ } from '@wordpress/i18n';
|
|
297
|
-
import { useBlockProps, InspectorControls, RichText, BlockControls, AlignmentToolbar } from '@wordpress/block-editor';
|
|
298
|
-
import { PanelBody, RangeControl, Button, Notice } from '@wordpress/components';
|
|
299
|
-
import { useState } from '@wordpress/element';
|
|
300
|
-
import currentManifest from './manifest-document';
|
|
301
|
-
import {
|
|
302
|
-
InspectorFromManifest,
|
|
303
|
-
useEditorFields,
|
|
304
|
-
useTypedAttributeUpdater,
|
|
305
|
-
} from '@wp-typia/block-runtime/inspector';
|
|
306
|
-
import type { {{pascalCase}}Attributes } from './types';
|
|
307
|
-
import {
|
|
308
|
-
sanitize{{pascalCase}}Attributes,
|
|
309
|
-
validate{{pascalCase}}Attributes,
|
|
310
|
-
} from './validators';
|
|
311
|
-
import { useTypiaValidation } from './hooks';
|
|
312
|
-
|
|
313
|
-
type EditProps = BlockEditProps<{{pascalCase}}Attributes>;
|
|
314
|
-
|
|
315
|
-
const actionButtonRowStyle = { display: 'flex', gap: '8px', marginTop: '16px' };
|
|
316
|
-
const validationListStyle = { margin: 0, paddingLeft: '1em' };
|
|
317
|
-
|
|
318
|
-
export default function Edit({ attributes, setAttributes, isSelected }: EditProps) {
|
|
319
|
-
const [isPreviewing, setIsPreviewing] = useState(false);
|
|
320
|
-
const editorFields = useEditorFields(currentManifest, {
|
|
321
|
-
manual: ['content', 'clickCount', 'maxClicks'],
|
|
322
|
-
labels: {
|
|
323
|
-
alignment: __('Alignment', '{{textDomain}}'),
|
|
324
|
-
animation: __('Animation', '{{textDomain}}'),
|
|
325
|
-
interactiveMode: __('Interactive Mode', '{{textDomain}}'),
|
|
326
|
-
isVisible: __('Visible', '{{textDomain}}'),
|
|
327
|
-
showCounter: __('Show Counter', '{{textDomain}}'),
|
|
328
|
-
},
|
|
329
|
-
});
|
|
330
|
-
const { errorMessages, isValid } = useTypiaValidation(
|
|
331
|
-
attributes,
|
|
332
|
-
validate{{pascalCase}}Attributes,
|
|
333
|
-
);
|
|
334
|
-
const validateEditorUpdate = (nextAttributes: {{pascalCase}}Attributes) => {
|
|
335
|
-
try {
|
|
336
|
-
return {
|
|
337
|
-
data: sanitize{{pascalCase}}Attributes(nextAttributes),
|
|
338
|
-
errors: [],
|
|
339
|
-
isValid: true as const,
|
|
340
|
-
};
|
|
341
|
-
} catch {
|
|
342
|
-
return validate{{pascalCase}}Attributes(nextAttributes);
|
|
343
|
-
}
|
|
344
|
-
};
|
|
345
|
-
const { updateField } = useTypedAttributeUpdater(
|
|
346
|
-
attributes,
|
|
347
|
-
setAttributes,
|
|
348
|
-
validateEditorUpdate
|
|
349
|
-
);
|
|
350
|
-
const alignmentValue = editorFields.getStringValue(
|
|
351
|
-
attributes,
|
|
352
|
-
'alignment',
|
|
353
|
-
'left'
|
|
354
|
-
) as NonNullable<{{pascalCase}}Attributes['alignment']>;
|
|
355
|
-
const clickCount = attributes.clickCount ?? 0;
|
|
356
|
-
const isVisible = editorFields.getBooleanValue(
|
|
357
|
-
attributes,
|
|
358
|
-
'isVisible',
|
|
359
|
-
true
|
|
360
|
-
);
|
|
361
|
-
const isAnimating = attributes.isAnimating ?? false;
|
|
362
|
-
const maxClicks = attributes.maxClicks ?? 0;
|
|
363
|
-
const showCounter = editorFields.getBooleanValue(
|
|
364
|
-
attributes,
|
|
365
|
-
'showCounter',
|
|
366
|
-
true
|
|
367
|
-
);
|
|
368
|
-
const interactiveMode = editorFields.getStringValue(
|
|
369
|
-
attributes,
|
|
370
|
-
'interactiveMode',
|
|
371
|
-
'click'
|
|
372
|
-
) as NonNullable<{{pascalCase}}Attributes['interactiveMode']>;
|
|
373
|
-
const animation = editorFields.getStringValue(
|
|
374
|
-
attributes,
|
|
375
|
-
'animation',
|
|
376
|
-
'none'
|
|
377
|
-
) as NonNullable<{{pascalCase}}Attributes['animation']>;
|
|
378
|
-
|
|
379
|
-
const blockProps = useBlockProps({
|
|
380
|
-
className: \`{{cssClassName}} {{cssClassName}}--\${interactiveMode}\`,
|
|
381
|
-
'data-wp-interactive': '{{slugKebabCase}}',
|
|
382
|
-
'data-wp-context': JSON.stringify({
|
|
383
|
-
clicks: clickCount,
|
|
384
|
-
isAnimating,
|
|
385
|
-
isVisible,
|
|
386
|
-
animation,
|
|
387
|
-
maxClicks,
|
|
388
|
-
})
|
|
389
|
-
});
|
|
390
|
-
const previewContentStyle = { textAlign: alignmentValue };
|
|
391
|
-
const progressBarStyle = { width: \`\${(clickCount / maxClicks) * 100}%\` };
|
|
392
|
-
|
|
393
|
-
const resetCounter = () => {
|
|
394
|
-
updateField('clickCount', 0);
|
|
395
|
-
updateField('isAnimating', false);
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
const testAnimation = () => {
|
|
399
|
-
updateField('isAnimating', true);
|
|
400
|
-
setTimeout(() => {
|
|
401
|
-
updateField('isAnimating', false);
|
|
402
|
-
}, 1000);
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
return (
|
|
406
|
-
<>
|
|
407
|
-
<BlockControls>
|
|
408
|
-
<AlignmentToolbar
|
|
409
|
-
value={alignmentValue}
|
|
410
|
-
onChange={(value) => updateField('alignment', (value || alignmentValue) as NonNullable<{{pascalCase}}Attributes['alignment']>)}
|
|
411
|
-
/>
|
|
412
|
-
</BlockControls>
|
|
413
|
-
|
|
414
|
-
<InspectorControls>
|
|
415
|
-
<InspectorFromManifest
|
|
416
|
-
attributes={attributes}
|
|
417
|
-
fieldLookup={editorFields}
|
|
418
|
-
onChange={updateField}
|
|
419
|
-
paths={['alignment', 'interactiveMode', 'animation', 'showCounter', 'isVisible']}
|
|
420
|
-
title={__('Interactive Settings', '{{textDomain}}')}
|
|
421
|
-
/>
|
|
422
|
-
|
|
423
|
-
<PanelBody title={__('Counter Settings', '{{textDomain}}')}>
|
|
424
|
-
<RangeControl
|
|
425
|
-
label={__('Max Clicks', '{{textDomain}}')}
|
|
426
|
-
value={maxClicks}
|
|
427
|
-
onChange={(value) => updateField('maxClicks', value ?? maxClicks)}
|
|
428
|
-
min={0}
|
|
429
|
-
max={100}
|
|
430
|
-
help={__('Set to 0 for unlimited clicks', '{{textDomain}}')}
|
|
431
|
-
/>
|
|
432
|
-
|
|
433
|
-
<div style={actionButtonRowStyle}>
|
|
434
|
-
<Button
|
|
435
|
-
variant="secondary"
|
|
436
|
-
onClick={resetCounter}
|
|
437
|
-
isSmall
|
|
438
|
-
>
|
|
439
|
-
{__('Reset Counter', '{{textDomain}}')}
|
|
440
|
-
</Button>
|
|
441
|
-
<Button
|
|
442
|
-
variant="secondary"
|
|
443
|
-
onClick={testAnimation}
|
|
444
|
-
isSmall
|
|
445
|
-
>
|
|
446
|
-
{__('Test Animation', '{{textDomain}}')}
|
|
447
|
-
</Button>
|
|
448
|
-
</div>
|
|
449
|
-
</PanelBody>
|
|
450
|
-
|
|
451
|
-
{!isValid && (
|
|
452
|
-
<PanelBody title={__('Validation Errors', '{{textDomain}}')} initialOpen>
|
|
453
|
-
{errorMessages.map((error, index) => (
|
|
454
|
-
<Notice key={index} status="error" isDismissible={false}>
|
|
455
|
-
{error}
|
|
456
|
-
</Notice>
|
|
457
|
-
))}
|
|
458
|
-
</PanelBody>
|
|
459
|
-
)}
|
|
460
|
-
|
|
461
|
-
{isSelected && (
|
|
462
|
-
<PanelBody title={__('Preview', '{{textDomain}}')}>
|
|
463
|
-
<Button
|
|
464
|
-
variant={isPreviewing ? 'primary' : 'secondary'}
|
|
465
|
-
onClick={() => setIsPreviewing(!isPreviewing)}
|
|
466
|
-
aria-pressed={isPreviewing}
|
|
467
|
-
isSmall
|
|
468
|
-
>
|
|
469
|
-
{isPreviewing
|
|
470
|
-
? __('Disable Preview Mode', '{{textDomain}}')
|
|
471
|
-
: __('Enable Preview Mode', '{{textDomain}}')}
|
|
472
|
-
</Button>
|
|
473
|
-
|
|
474
|
-
{clickCount > 0 && (
|
|
475
|
-
<Notice status="info" isDismissible={false}>
|
|
476
|
-
{__('Current clicks:', '{{textDomain}}')} {clickCount}
|
|
477
|
-
{maxClicks > 0 && \` / \${maxClicks}\`}
|
|
478
|
-
</Notice>
|
|
479
|
-
)}
|
|
480
|
-
</PanelBody>
|
|
481
|
-
)}
|
|
482
|
-
</InspectorControls>
|
|
483
|
-
|
|
484
|
-
<div {...blockProps}>
|
|
485
|
-
<div
|
|
486
|
-
className={\`{{cssClassName}}__content \${isAnimating ? 'is-animating' : ''}\`}
|
|
487
|
-
style={previewContentStyle}
|
|
488
|
-
data-wp-on--click={isPreviewing ? 'actions.handleClick' : undefined}
|
|
489
|
-
data-wp-on--mouseenter={isPreviewing && interactiveMode === 'hover' ? 'actions.handleMouseEnter' : undefined}
|
|
490
|
-
data-wp-on--mouseleave={isPreviewing && interactiveMode === 'hover' ? 'actions.handleMouseLeave' : undefined}
|
|
491
|
-
>
|
|
492
|
-
<RichText
|
|
493
|
-
tagName="p"
|
|
494
|
-
value={attributes.content}
|
|
495
|
-
onChange={(value) => updateField('content', value)}
|
|
496
|
-
placeholder={__( {{titleJson}} + ' – click me to interact!', '{{textDomain}}')}
|
|
497
|
-
/>
|
|
498
|
-
|
|
499
|
-
{!isValid && (
|
|
500
|
-
<Notice status="error" isDismissible={false}>
|
|
501
|
-
<p>
|
|
502
|
-
<strong>{__('Validation Errors', '{{textDomain}}')}</strong>
|
|
503
|
-
</p>
|
|
504
|
-
<ul style={validationListStyle}>
|
|
505
|
-
{errorMessages.map((error, index) => (
|
|
506
|
-
<li key={index}>{error}</li>
|
|
507
|
-
))}
|
|
508
|
-
</ul>
|
|
509
|
-
</Notice>
|
|
510
|
-
)}
|
|
511
|
-
|
|
512
|
-
{showCounter && (
|
|
513
|
-
<div className="{{cssClassName}}__counter">
|
|
514
|
-
<span className="{{cssClassName}}__counter-label">
|
|
515
|
-
{__('Clicks:', '{{textDomain}}')}
|
|
516
|
-
</span>
|
|
517
|
-
<span
|
|
518
|
-
className="{{cssClassName}}__counter-value"
|
|
519
|
-
data-wp-text="state.clicks"
|
|
520
|
-
>
|
|
521
|
-
{clickCount}
|
|
522
|
-
</span>
|
|
523
|
-
</div>
|
|
524
|
-
)}
|
|
525
|
-
|
|
526
|
-
{maxClicks > 0 && (
|
|
527
|
-
<div className="{{cssClassName}}__progress">
|
|
528
|
-
<div
|
|
529
|
-
className="{{cssClassName}}__progress-bar"
|
|
530
|
-
style={progressBarStyle}
|
|
531
|
-
data-wp-style--width="state.progress + '%'"
|
|
532
|
-
/>
|
|
533
|
-
</div>
|
|
534
|
-
)}
|
|
535
|
-
|
|
536
|
-
{animation !== 'none' && (
|
|
537
|
-
<div
|
|
538
|
-
className={\`{{cssClassName}}__animation \${isAnimating ? 'is-active' : ''}\`}
|
|
539
|
-
data-wp-class--is-active="state.isAnimating"
|
|
540
|
-
>
|
|
541
|
-
{animation}
|
|
542
|
-
</div>
|
|
543
|
-
)}
|
|
544
|
-
</div>
|
|
545
|
-
</div>
|
|
546
|
-
</>
|
|
547
|
-
);
|
|
548
|
-
}
|
|
549
|
-
`;
|
|
550
|
-
export const INTERACTIVITY_SAVE_TEMPLATE = `import { useBlockProps, RichText } from '@wordpress/block-editor';
|
|
551
|
-
import { __ } from '@wordpress/i18n';
|
|
552
|
-
import type { {{pascalCase}}Attributes } from './types';
|
|
553
|
-
|
|
554
|
-
export default function Save({ attributes }: { attributes: {{pascalCase}}Attributes }) {
|
|
555
|
-
const clickCount = attributes.clickCount ?? 0;
|
|
556
|
-
const interactiveMode = attributes.interactiveMode ?? 'click';
|
|
557
|
-
const animation = attributes.animation ?? 'none';
|
|
558
|
-
const isAnimating = attributes.isAnimating ?? false;
|
|
559
|
-
const isVisible = attributes.isVisible ?? true;
|
|
560
|
-
const maxClicks = attributes.maxClicks ?? 0;
|
|
561
|
-
const showCounter = attributes.showCounter ?? true;
|
|
562
|
-
const contentStyle = { textAlign: attributes.alignment };
|
|
563
|
-
const blockProps = useBlockProps.save({
|
|
564
|
-
className: \`{{cssClassName}} {{cssClassName}}--\${interactiveMode}\`,
|
|
565
|
-
'data-wp-interactive': '{{slugKebabCase}}',
|
|
566
|
-
'data-wp-context': JSON.stringify({
|
|
567
|
-
clicks: clickCount,
|
|
568
|
-
isAnimating,
|
|
569
|
-
isVisible,
|
|
570
|
-
animation,
|
|
571
|
-
maxClicks,
|
|
572
|
-
})
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
return (
|
|
576
|
-
<div {...blockProps}>
|
|
577
|
-
<div
|
|
578
|
-
className={\`{{cssClassName}}__content \${isAnimating ? 'is-animating' : ''}\`}
|
|
579
|
-
style={contentStyle}
|
|
580
|
-
data-wp-on--click="actions.handleClick"
|
|
581
|
-
data-wp-on--mouseenter={interactiveMode === 'hover' ? 'actions.handleMouseEnter' : undefined}
|
|
582
|
-
data-wp-on--mouseleave={interactiveMode === 'hover' ? 'actions.handleMouseLeave' : undefined}
|
|
583
|
-
data-wp-bind--hidden="!state.isVisible"
|
|
584
|
-
>
|
|
585
|
-
<RichText.Content
|
|
586
|
-
tagName="p"
|
|
587
|
-
value={attributes.content}
|
|
588
|
-
className="{{cssClassName}}__text"
|
|
589
|
-
/>
|
|
590
|
-
|
|
591
|
-
{showCounter && (
|
|
592
|
-
<div
|
|
593
|
-
className="{{cssClassName}}__counter"
|
|
594
|
-
role="status"
|
|
595
|
-
aria-live="polite"
|
|
596
|
-
aria-atomic="true"
|
|
597
|
-
>
|
|
598
|
-
<span className="{{cssClassName}}__counter-label">
|
|
599
|
-
{ __( 'Clicks:', '{{textDomain}}' ) }
|
|
600
|
-
</span>
|
|
601
|
-
<span
|
|
602
|
-
className="{{cssClassName}}__counter-value"
|
|
603
|
-
data-wp-text="state.clicks"
|
|
604
|
-
>
|
|
605
|
-
{clickCount}
|
|
606
|
-
</span>
|
|
607
|
-
</div>
|
|
608
|
-
)}
|
|
609
|
-
|
|
610
|
-
{maxClicks > 0 && (
|
|
611
|
-
<div className="{{cssClassName}}__progress">
|
|
612
|
-
<div
|
|
613
|
-
className="{{cssClassName}}__progress-bar"
|
|
614
|
-
role="progressbar"
|
|
615
|
-
aria-label={ __( 'Click progress', '{{textDomain}}' ) }
|
|
616
|
-
aria-valuemin={0}
|
|
617
|
-
aria-valuemax={maxClicks}
|
|
618
|
-
aria-valuenow={Math.min(clickCount, maxClicks)}
|
|
619
|
-
data-wp-bind--aria-valuenow="state.clampedClicks"
|
|
620
|
-
data-wp-style--width="state.progress + '%'"
|
|
621
|
-
/>
|
|
622
|
-
</div>
|
|
623
|
-
)}
|
|
624
|
-
|
|
625
|
-
<div
|
|
626
|
-
className={\`{{cssClassName}}__animation \${animation}\`}
|
|
627
|
-
aria-hidden="true"
|
|
628
|
-
data-wp-class--is-active="state.isAnimating"
|
|
629
|
-
/>
|
|
630
|
-
|
|
631
|
-
{maxClicks > 0 && (
|
|
632
|
-
<div
|
|
633
|
-
className="{{cssClassName}}__completion"
|
|
634
|
-
role="status"
|
|
635
|
-
aria-live="polite"
|
|
636
|
-
aria-atomic="true"
|
|
637
|
-
data-wp-bind--hidden="!state.isComplete"
|
|
638
|
-
>
|
|
639
|
-
{ __( '🎉 Complete!', '{{textDomain}}' ) }
|
|
640
|
-
</div>
|
|
641
|
-
)}
|
|
642
|
-
|
|
643
|
-
<button
|
|
644
|
-
className="{{cssClassName}}__reset"
|
|
645
|
-
data-wp-on--click="actions.reset"
|
|
646
|
-
aria-label={ __( 'Reset counter', '{{textDomain}}' ) }
|
|
647
|
-
>
|
|
648
|
-
<span aria-hidden="true">↻</span>
|
|
649
|
-
<span className="screen-reader-text">
|
|
650
|
-
{ __( 'Reset counter', '{{textDomain}}' ) }
|
|
651
|
-
</span>
|
|
652
|
-
</button>
|
|
653
|
-
</div>
|
|
654
|
-
</div>
|
|
655
|
-
);
|
|
656
|
-
}
|
|
657
|
-
`;
|
|
658
|
-
export const INTERACTIVITY_INDEX_TEMPLATE = `import {
|
|
659
|
-
registerScaffoldBlockType,
|
|
660
|
-
type BlockConfiguration,
|
|
661
|
-
} from '@wp-typia/block-types/blocks/registration';
|
|
662
|
-
import type { BlockSupports } from '@wp-typia/block-types/blocks/supports';
|
|
663
|
-
import {
|
|
664
|
-
buildScaffoldBlockRegistration,
|
|
665
|
-
parseScaffoldBlockMetadata,
|
|
666
|
-
} from '@wp-typia/block-runtime/blocks';
|
|
667
|
-
|
|
668
|
-
import Edit from './edit';
|
|
669
|
-
import Save from './save';
|
|
670
|
-
import metadata from './block-metadata';
|
|
671
|
-
import './editor.scss';
|
|
672
|
-
import './style.scss';
|
|
673
|
-
|
|
674
|
-
import type { {{pascalCase}}Attributes } from './types';
|
|
675
|
-
|
|
676
|
-
const scaffoldSupports = {
|
|
677
|
-
html: false,
|
|
678
|
-
align: true,
|
|
679
|
-
anchor: true,
|
|
680
|
-
className: true,
|
|
681
|
-
interactivity: true,
|
|
682
|
-
} satisfies BlockSupports;
|
|
683
|
-
|
|
684
|
-
const registration = buildScaffoldBlockRegistration(
|
|
685
|
-
parseScaffoldBlockMetadata<BlockConfiguration<{{pascalCase}}Attributes>>(metadata),
|
|
686
|
-
{
|
|
687
|
-
supports: scaffoldSupports,
|
|
688
|
-
edit: Edit,
|
|
689
|
-
save: Save,
|
|
690
|
-
}
|
|
691
|
-
);
|
|
692
|
-
|
|
693
|
-
registerScaffoldBlockType(registration.name, registration.settings);
|
|
694
|
-
`;
|
|
695
|
-
export const INTERACTIVITY_SCRIPT_TEMPLATE = `/**
|
|
696
|
-
* WordPress Interactivity API implementation for {{title}} block
|
|
697
|
-
*/
|
|
698
|
-
import { store, getContext, getElement, withSyncEvent } from '@wordpress/interactivity';
|
|
699
|
-
import type { {{pascalCase}}Context } from './types';
|
|
700
|
-
|
|
701
|
-
function getBlockContext() {
|
|
702
|
-
return getContext<{{pascalCase}}Context>();
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// Store configuration
|
|
706
|
-
store('{{slugKebabCase}}', {
|
|
707
|
-
// State - reactive data that updates the UI
|
|
708
|
-
state: {
|
|
709
|
-
get clicks() {
|
|
710
|
-
return getBlockContext().clicks;
|
|
711
|
-
},
|
|
712
|
-
get isAnimating() {
|
|
713
|
-
return getBlockContext().isAnimating;
|
|
714
|
-
},
|
|
715
|
-
get isVisible() {
|
|
716
|
-
return getBlockContext().isVisible;
|
|
717
|
-
},
|
|
718
|
-
get progress() {
|
|
719
|
-
const context = getBlockContext();
|
|
720
|
-
const clampedClicks =
|
|
721
|
-
context.maxClicks > 0
|
|
722
|
-
? Math.min(context.clicks, context.maxClicks)
|
|
723
|
-
: context.clicks;
|
|
724
|
-
return context.maxClicks > 0 ? (clampedClicks / context.maxClicks) * 100 : 0;
|
|
725
|
-
},
|
|
726
|
-
get clampedClicks() {
|
|
727
|
-
const context = getBlockContext();
|
|
728
|
-
return context.maxClicks > 0
|
|
729
|
-
? Math.min(context.clicks, context.maxClicks)
|
|
730
|
-
: context.clicks;
|
|
731
|
-
},
|
|
732
|
-
get isComplete() {
|
|
733
|
-
const context = getBlockContext();
|
|
734
|
-
return context.clicks >= context.maxClicks && context.maxClicks > 0;
|
|
735
|
-
}
|
|
736
|
-
},
|
|
737
|
-
|
|
738
|
-
// Actions - user interactions
|
|
739
|
-
actions: {
|
|
740
|
-
// Handle block click
|
|
741
|
-
handleClick: () => {
|
|
742
|
-
const context = getBlockContext();
|
|
743
|
-
const { ref } = getElement();
|
|
744
|
-
|
|
745
|
-
if (!ref) {
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
if (context.maxClicks > 0 && context.clicks >= context.maxClicks) {
|
|
750
|
-
return;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
const previousClicks = context.clicks;
|
|
754
|
-
|
|
755
|
-
// Increment click counter
|
|
756
|
-
context.clicks += 1;
|
|
757
|
-
|
|
758
|
-
// Trigger animation
|
|
759
|
-
if (context.animation !== 'none') {
|
|
760
|
-
context.isAnimating = true;
|
|
761
|
-
setTimeout(() => {
|
|
762
|
-
context.isAnimating = false;
|
|
763
|
-
}, 1000);
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// Emit custom event
|
|
767
|
-
ref.dispatchEvent(new CustomEvent('{{slugKebabCase}}:click', {
|
|
768
|
-
detail: { clicks: context.clicks }
|
|
769
|
-
}));
|
|
770
|
-
|
|
771
|
-
// Check if max clicks reached
|
|
772
|
-
if (context.maxClicks > 0 && previousClicks < context.maxClicks && context.clicks === context.maxClicks) {
|
|
773
|
-
ref.dispatchEvent(new CustomEvent('{{slugKebabCase}}:complete', {
|
|
774
|
-
detail: { totalClicks: context.clicks }
|
|
775
|
-
}));
|
|
776
|
-
}
|
|
777
|
-
},
|
|
778
|
-
|
|
779
|
-
// Handle hover events
|
|
780
|
-
handleMouseEnter: () => {
|
|
781
|
-
const context = getBlockContext();
|
|
782
|
-
if (context.animation === 'none') return;
|
|
783
|
-
context.isAnimating = true;
|
|
784
|
-
},
|
|
785
|
-
|
|
786
|
-
handleMouseLeave: () => {
|
|
787
|
-
const context = getBlockContext();
|
|
788
|
-
if (context.animation === 'none') return;
|
|
789
|
-
context.isAnimating = false;
|
|
790
|
-
},
|
|
791
|
-
|
|
792
|
-
// Reset counter
|
|
793
|
-
reset: withSyncEvent((event: Event) => {
|
|
794
|
-
event.stopPropagation();
|
|
795
|
-
const context = getBlockContext();
|
|
796
|
-
context.clicks = 0;
|
|
797
|
-
context.isAnimating = false;
|
|
798
|
-
})
|
|
799
|
-
}
|
|
800
|
-
});
|
|
801
|
-
`;
|
|
802
|
-
export const INTERACTIVITY_VALIDATORS_TEMPLATE = `import typia from 'typia';
|
|
803
|
-
import currentManifest from "./manifest-defaults-document";
|
|
804
|
-
import { {{pascalCase}}Attributes, {{pascalCase}}ValidationResult } from "./types";
|
|
805
|
-
import { createTemplateValidatorToolkit } from "./validator-toolkit";
|
|
806
|
-
|
|
807
|
-
const scaffoldValidators = createTemplateValidatorToolkit<{{pascalCase}}Attributes>({
|
|
808
|
-
assert: typia.createAssert<{{pascalCase}}Attributes>(),
|
|
809
|
-
clone: typia.misc.createClone<{{pascalCase}}Attributes>() as (
|
|
810
|
-
value: {{pascalCase}}Attributes,
|
|
811
|
-
) => {{pascalCase}}Attributes,
|
|
812
|
-
is: typia.createIs<{{pascalCase}}Attributes>(),
|
|
813
|
-
manifest: currentManifest,
|
|
814
|
-
prune: typia.misc.createPrune<{{pascalCase}}Attributes>(),
|
|
815
|
-
random: typia.createRandom<{{pascalCase}}Attributes>() as (
|
|
816
|
-
...args: unknown[]
|
|
817
|
-
) => {{pascalCase}}Attributes,
|
|
818
|
-
validate: typia.createValidate<{{pascalCase}}Attributes>(),
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
export const validate{{pascalCase}}Attributes =
|
|
822
|
-
scaffoldValidators.validateAttributes as (
|
|
823
|
-
attributes: unknown,
|
|
824
|
-
) => {{pascalCase}}ValidationResult;
|
|
825
|
-
|
|
826
|
-
export const validators = scaffoldValidators.validators;
|
|
827
|
-
|
|
828
|
-
export const sanitize{{pascalCase}}Attributes =
|
|
829
|
-
scaffoldValidators.sanitizeAttributes as (
|
|
830
|
-
attributes: Partial<{{pascalCase}}Attributes>,
|
|
831
|
-
) => {{pascalCase}}Attributes;
|
|
832
|
-
|
|
833
|
-
/**
|
|
834
|
-
* Runtime type guard for checking if an object is {{pascalCase}}Attributes.
|
|
835
|
-
*/
|
|
836
|
-
export const is{{pascalCase}}Attributes = (obj: unknown): obj is {{pascalCase}}Attributes => {
|
|
837
|
-
return validators.is(obj);
|
|
838
|
-
};
|
|
839
|
-
|
|
840
|
-
export const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;
|
|
841
|
-
`;
|
|
842
|
-
export const PERSISTENCE_EDIT_TEMPLATE = `import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';
|
|
843
|
-
import { __ } from '@wordpress/i18n';
|
|
844
|
-
import {
|
|
845
|
-
AlignmentToolbar,
|
|
846
|
-
BlockControls,
|
|
847
|
-
InspectorControls,
|
|
848
|
-
RichText,
|
|
849
|
-
useBlockProps,
|
|
850
|
-
} from '@wordpress/block-editor';
|
|
851
|
-
import {
|
|
852
|
-
Notice,
|
|
853
|
-
PanelBody,
|
|
854
|
-
TextControl,
|
|
855
|
-
} from '@wordpress/components';
|
|
856
|
-
import currentManifest from './manifest-document';
|
|
857
|
-
import {
|
|
858
|
-
InspectorFromManifest,
|
|
859
|
-
useEditorFields,
|
|
860
|
-
useTypedAttributeUpdater,
|
|
861
|
-
} from '@wp-typia/block-runtime/inspector';
|
|
862
|
-
import type { {{pascalCase}}Attributes } from './types';
|
|
863
|
-
import {
|
|
864
|
-
sanitize{{pascalCase}}Attributes,
|
|
865
|
-
validate{{pascalCase}}Attributes,
|
|
866
|
-
} from './validators';
|
|
867
|
-
import { useTypiaValidation } from './hooks';
|
|
868
|
-
|
|
869
|
-
type EditProps = BlockEditProps< {{pascalCase}}Attributes >;
|
|
870
|
-
|
|
871
|
-
export default function Edit( {
|
|
872
|
-
attributes,
|
|
873
|
-
setAttributes,
|
|
874
|
-
}: EditProps ) {
|
|
875
|
-
const editorFields = useEditorFields(
|
|
876
|
-
currentManifest,
|
|
877
|
-
{
|
|
878
|
-
manual: [ 'content', 'resourceKey' ],
|
|
879
|
-
labels: {
|
|
880
|
-
buttonLabel: __( 'Button Label', '{{textDomain}}' ),
|
|
881
|
-
resourceKey: __( 'Resource Key', '{{textDomain}}' ),
|
|
882
|
-
showCount: __( 'Show Count', '{{textDomain}}' ),
|
|
883
|
-
},
|
|
884
|
-
}
|
|
885
|
-
);
|
|
886
|
-
const { errorMessages, isValid } = useTypiaValidation(
|
|
887
|
-
attributes,
|
|
888
|
-
validate{{pascalCase}}Attributes
|
|
889
|
-
);
|
|
890
|
-
const validateEditorUpdate = (
|
|
891
|
-
nextAttributes: {{pascalCase}}Attributes
|
|
892
|
-
) => {
|
|
893
|
-
try {
|
|
894
|
-
return {
|
|
895
|
-
data: sanitize{{pascalCase}}Attributes( nextAttributes ),
|
|
896
|
-
errors: [],
|
|
897
|
-
isValid: true as const,
|
|
898
|
-
};
|
|
899
|
-
} catch {
|
|
900
|
-
return validate{{pascalCase}}Attributes( nextAttributes );
|
|
901
|
-
}
|
|
902
|
-
};
|
|
903
|
-
const { updateField } = useTypedAttributeUpdater(
|
|
904
|
-
attributes,
|
|
905
|
-
setAttributes,
|
|
906
|
-
validateEditorUpdate
|
|
907
|
-
);
|
|
908
|
-
const alignmentValue = editorFields.getStringValue(
|
|
909
|
-
attributes,
|
|
910
|
-
'alignment',
|
|
911
|
-
'left'
|
|
912
|
-
);
|
|
913
|
-
const persistencePolicy = '{{persistencePolicy}}';
|
|
914
|
-
const persistencePolicyDescription = __(
|
|
915
|
-
{{persistencePolicyDescriptionJson}},
|
|
916
|
-
'{{textDomain}}'
|
|
917
|
-
);
|
|
918
|
-
|
|
919
|
-
return (
|
|
920
|
-
<>
|
|
921
|
-
<BlockControls>
|
|
922
|
-
<AlignmentToolbar
|
|
923
|
-
value={ alignmentValue }
|
|
924
|
-
onChange={ ( value ) =>
|
|
925
|
-
updateField(
|
|
926
|
-
'alignment',
|
|
927
|
-
( value || alignmentValue ) as NonNullable< {{pascalCase}}Attributes[ 'alignment' ] >
|
|
928
|
-
)
|
|
929
|
-
}
|
|
930
|
-
/>
|
|
931
|
-
</BlockControls>
|
|
932
|
-
<InspectorControls>
|
|
933
|
-
<InspectorFromManifest
|
|
934
|
-
attributes={ attributes }
|
|
935
|
-
fieldLookup={ editorFields }
|
|
936
|
-
onChange={ updateField }
|
|
937
|
-
paths={ [ 'alignment', 'isVisible', 'showCount', 'buttonLabel' ] }
|
|
938
|
-
title={ __( 'Persistence Settings', '{{textDomain}}' ) }
|
|
939
|
-
>
|
|
940
|
-
<TextControl
|
|
941
|
-
label={ __( 'Resource Key', '{{textDomain}}' ) }
|
|
942
|
-
value={ attributes.resourceKey ?? '' }
|
|
943
|
-
onChange={ ( value ) => updateField( 'resourceKey', value ) }
|
|
944
|
-
help={ __( 'Stable persisted identifier used by the storage-backed counter endpoint.', '{{textDomain}}' ) }
|
|
945
|
-
/>
|
|
946
|
-
<Notice status="info" isDismissible={ false }>
|
|
947
|
-
{ __( 'Storage mode: {{dataStorageMode}}', '{{textDomain}}' ) }
|
|
948
|
-
</Notice>
|
|
949
|
-
<Notice status="info" isDismissible={ false }>
|
|
950
|
-
{ __( 'Persistence policy: {{persistencePolicy}}', '{{textDomain}}' ) }
|
|
951
|
-
<br />
|
|
952
|
-
{ persistencePolicyDescription }
|
|
953
|
-
</Notice>
|
|
954
|
-
<Notice status="info" isDismissible={ false }>
|
|
955
|
-
{ __( 'Render mode: dynamic. \`render.php\` bootstraps durable post context, while fresh session-only write data is loaded from the dedicated \`/bootstrap\` endpoint after hydration.', '{{textDomain}}' ) }
|
|
956
|
-
</Notice>
|
|
957
|
-
</InspectorFromManifest>
|
|
958
|
-
{ ! isValid && (
|
|
959
|
-
<PanelBody
|
|
960
|
-
title={ __( 'Validation Errors', '{{textDomain}}' ) }
|
|
961
|
-
initialOpen
|
|
962
|
-
>
|
|
963
|
-
{ errorMessages.map( ( error, index ) => (
|
|
964
|
-
<Notice key={ index } status="error" isDismissible={ false }>
|
|
965
|
-
{ error }
|
|
966
|
-
</Notice>
|
|
967
|
-
) ) }
|
|
968
|
-
</PanelBody>
|
|
969
|
-
) }
|
|
970
|
-
</InspectorControls>
|
|
971
|
-
<div
|
|
972
|
-
{ ...useBlockProps( {
|
|
973
|
-
className: '{{cssClassName}}',
|
|
974
|
-
style: {
|
|
975
|
-
textAlign:
|
|
976
|
-
alignmentValue as NonNullable< {{pascalCase}}Attributes[ 'alignment' ] >,
|
|
977
|
-
},
|
|
978
|
-
} ) }
|
|
979
|
-
>
|
|
980
|
-
<RichText
|
|
981
|
-
tagName="p"
|
|
982
|
-
value={ attributes.content }
|
|
983
|
-
onChange={ ( value ) => updateField( 'content', value ) }
|
|
984
|
-
placeholder={ __( {{titleJson}} + ' persistence block', '{{textDomain}}' ) }
|
|
985
|
-
/>
|
|
986
|
-
<p className="{{cssClassName}}__meta">
|
|
987
|
-
{ __( 'Resource key:', '{{textDomain}}' ) } { attributes.resourceKey || '—' }
|
|
988
|
-
</p>
|
|
989
|
-
<p className="{{cssClassName}}__meta">
|
|
990
|
-
{ __( 'Storage mode:', '{{textDomain}}' ) } {{dataStorageMode}}
|
|
991
|
-
</p>
|
|
992
|
-
<p className="{{cssClassName}}__meta">
|
|
993
|
-
{ __( 'Persistence policy:', '{{textDomain}}' ) } {{persistencePolicy}}
|
|
994
|
-
</p>
|
|
995
|
-
{ ! isValid && (
|
|
996
|
-
<Notice status="error" isDismissible={ false }>
|
|
997
|
-
<ul>
|
|
998
|
-
{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }
|
|
999
|
-
</ul>
|
|
1000
|
-
</Notice>
|
|
1001
|
-
) }
|
|
1002
|
-
</div>
|
|
1003
|
-
</>
|
|
1004
|
-
);
|
|
1005
|
-
}
|
|
1006
|
-
`;
|
|
1007
|
-
export const PERSISTENCE_INDEX_TEMPLATE = `import {
|
|
1008
|
-
\tregisterScaffoldBlockType,
|
|
1009
|
-
\ttype BlockConfiguration,
|
|
1010
|
-
} from '@wp-typia/block-types/blocks/registration';
|
|
1011
|
-
import {
|
|
1012
|
-
buildScaffoldBlockRegistration,
|
|
1013
|
-
parseScaffoldBlockMetadata,
|
|
1014
|
-
} from '@wp-typia/block-runtime/blocks';
|
|
1015
|
-
|
|
1016
|
-
import Edit from './edit';
|
|
1017
|
-
import Save from './save';
|
|
1018
|
-
import metadata from './block-metadata';
|
|
1019
|
-
import './style.scss';
|
|
1020
|
-
|
|
1021
|
-
import type { {{pascalCase}}Attributes } from './types';
|
|
1022
|
-
|
|
1023
|
-
const registration = buildScaffoldBlockRegistration(
|
|
1024
|
-
parseScaffoldBlockMetadata<BlockConfiguration< {{pascalCase}}Attributes >>( metadata ),
|
|
1025
|
-
{
|
|
1026
|
-
edit: Edit,
|
|
1027
|
-
save: Save,
|
|
1028
|
-
}
|
|
1029
|
-
);
|
|
1030
|
-
|
|
1031
|
-
registerScaffoldBlockType(registration.name, registration.settings);
|
|
1032
|
-
`;
|
|
1033
|
-
export const PERSISTENCE_SAVE_TEMPLATE = `export default function Save() {
|
|
1034
|
-
// This block is intentionally server-rendered. PHP bootstraps post context,
|
|
1035
|
-
// storage-backed state, and write-policy data before the frontend hydrates.
|
|
1036
|
-
return null;
|
|
1037
|
-
}
|
|
1038
|
-
`;
|
|
1039
|
-
export const PERSISTENCE_VALIDATORS_TEMPLATE = `import typia from 'typia';
|
|
1040
|
-
import currentManifest from './manifest-defaults-document';
|
|
1041
|
-
import type {
|
|
1042
|
-
{{pascalCase}}Attributes,
|
|
1043
|
-
{{pascalCase}}ValidationResult,
|
|
1044
|
-
} from './types';
|
|
1045
|
-
import { generateResourceKey } from '@wp-typia/block-runtime/identifiers';
|
|
1046
|
-
import { createTemplateValidatorToolkit } from './validator-toolkit';
|
|
1047
|
-
|
|
1048
|
-
const scaffoldValidators = createTemplateValidatorToolkit< {{pascalCase}}Attributes >( {
|
|
1049
|
-
assert: typia.createAssert< {{pascalCase}}Attributes >(),
|
|
1050
|
-
clone: typia.misc.createClone< {{pascalCase}}Attributes >() as (
|
|
1051
|
-
value: {{pascalCase}}Attributes,
|
|
1052
|
-
) => {{pascalCase}}Attributes,
|
|
1053
|
-
is: typia.createIs< {{pascalCase}}Attributes >(),
|
|
1054
|
-
manifest: currentManifest,
|
|
1055
|
-
prune: typia.misc.createPrune< {{pascalCase}}Attributes >(),
|
|
1056
|
-
random: typia.createRandom< {{pascalCase}}Attributes >() as (
|
|
1057
|
-
...args: unknown[]
|
|
1058
|
-
) => {{pascalCase}}Attributes,
|
|
1059
|
-
finalize: ( normalized ) => ( {
|
|
1060
|
-
...normalized,
|
|
1061
|
-
resourceKey:
|
|
1062
|
-
normalized.resourceKey && normalized.resourceKey.length > 0
|
|
1063
|
-
? normalized.resourceKey
|
|
1064
|
-
: generateResourceKey( '{{slugKebabCase}}' ),
|
|
1065
|
-
} ),
|
|
1066
|
-
validate: typia.createValidate< {{pascalCase}}Attributes >(),
|
|
1067
|
-
} );
|
|
1068
|
-
|
|
1069
|
-
export const validators = scaffoldValidators.validators;
|
|
1070
|
-
|
|
1071
|
-
export const validate{{pascalCase}}Attributes =
|
|
1072
|
-
scaffoldValidators.validateAttributes as (
|
|
1073
|
-
attributes: unknown
|
|
1074
|
-
) => {{pascalCase}}ValidationResult;
|
|
1075
|
-
|
|
1076
|
-
export const sanitize{{pascalCase}}Attributes =
|
|
1077
|
-
scaffoldValidators.sanitizeAttributes as (
|
|
1078
|
-
attributes: Partial< {{pascalCase}}Attributes >
|
|
1079
|
-
) => {{pascalCase}}Attributes;
|
|
1080
|
-
|
|
1081
|
-
export const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;
|
|
1082
|
-
`;
|
|
1083
|
-
export const PERSISTENCE_INTERACTIVITY_TEMPLATE = `import { getContext, store } from '@wordpress/interactivity';
|
|
1084
|
-
import { generatePublicWriteRequestId } from '@wp-typia/block-runtime/identifiers';
|
|
1085
|
-
|
|
1086
|
-
import { fetchBootstrap, fetchState, writeState } from './api';
|
|
1087
|
-
import type {
|
|
1088
|
-
{{pascalCase}}ClientState,
|
|
1089
|
-
{{pascalCase}}Context,
|
|
1090
|
-
{{pascalCase}}State,
|
|
1091
|
-
} from './types';
|
|
1092
|
-
import type {
|
|
1093
|
-
{{pascalCase}}WriteStateRequest,
|
|
1094
|
-
} from './api-types';
|
|
1095
|
-
|
|
1096
|
-
function hasExpiredPublicWriteToken(
|
|
1097
|
-
expiresAt?: number
|
|
1098
|
-
): boolean {
|
|
1099
|
-
return (
|
|
1100
|
-
typeof expiresAt === 'number' &&
|
|
1101
|
-
expiresAt > 0 &&
|
|
1102
|
-
Date.now() >= expiresAt * 1000
|
|
1103
|
-
);
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
function getWriteBlockedMessage(
|
|
1107
|
-
context: {{pascalCase}}Context
|
|
1108
|
-
): string {
|
|
1109
|
-
return context.persistencePolicy === 'authenticated'
|
|
1110
|
-
? 'Sign in to persist this counter.'
|
|
1111
|
-
: 'Public writes are temporarily unavailable.';
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
const BOOTSTRAP_MAX_ATTEMPTS = 3;
|
|
1115
|
-
const BOOTSTRAP_RETRY_DELAYS_MS = [ 250, 500 ];
|
|
1116
|
-
|
|
1117
|
-
async function waitForBootstrapRetry( delayMs: number ): Promise< void > {
|
|
1118
|
-
await new Promise( ( resolve ) => {
|
|
1119
|
-
setTimeout( resolve, delayMs );
|
|
1120
|
-
} );
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
function getClientState(
|
|
1124
|
-
context: {{pascalCase}}Context
|
|
1125
|
-
): {{pascalCase}}ClientState {
|
|
1126
|
-
if ( context.client ) {
|
|
1127
|
-
return context.client;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
context.client = {
|
|
1131
|
-
bootstrapError: '',
|
|
1132
|
-
writeExpiry: 0,
|
|
1133
|
-
writeNonce: '',
|
|
1134
|
-
writeToken: '',
|
|
1135
|
-
};
|
|
1136
|
-
|
|
1137
|
-
return context.client;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
function clearBootstrapError(
|
|
1141
|
-
context: {{pascalCase}}Context,
|
|
1142
|
-
clientState: {{pascalCase}}ClientState
|
|
1143
|
-
): void {
|
|
1144
|
-
if ( context.error === clientState.bootstrapError ) {
|
|
1145
|
-
context.error = '';
|
|
1146
|
-
}
|
|
1147
|
-
clientState.bootstrapError = '';
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
function setBootstrapError(
|
|
1151
|
-
context: {{pascalCase}}Context,
|
|
1152
|
-
clientState: {{pascalCase}}ClientState,
|
|
1153
|
-
message: string
|
|
1154
|
-
): void {
|
|
1155
|
-
clientState.bootstrapError = message;
|
|
1156
|
-
context.error = message;
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
const { actions, state } = store( '{{slugKebabCase}}', {
|
|
1160
|
-
state: {
|
|
1161
|
-
isHydrated: false,
|
|
1162
|
-
} as {{pascalCase}}State,
|
|
1163
|
-
|
|
1164
|
-
actions: {
|
|
1165
|
-
async loadState() {
|
|
1166
|
-
const context = getContext< {{pascalCase}}Context >();
|
|
1167
|
-
if ( context.postId <= 0 || ! context.resourceKey ) {
|
|
1168
|
-
return;
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
context.isLoading = true;
|
|
1172
|
-
context.error = '';
|
|
1173
|
-
|
|
1174
|
-
try {
|
|
1175
|
-
const result = await fetchState( {
|
|
1176
|
-
postId: context.postId,
|
|
1177
|
-
resourceKey: context.resourceKey,
|
|
1178
|
-
}, {
|
|
1179
|
-
transportTarget: 'frontend',
|
|
1180
|
-
} );
|
|
1181
|
-
if ( ! result.isValid || ! result.data ) {
|
|
1182
|
-
context.error = result.errors[ 0 ]?.expected ?? 'Unable to load counter';
|
|
1183
|
-
return;
|
|
1184
|
-
}
|
|
1185
|
-
context.count = result.data.count;
|
|
1186
|
-
} catch ( error ) {
|
|
1187
|
-
context.error =
|
|
1188
|
-
error instanceof Error ? error.message : 'Unknown loading error';
|
|
1189
|
-
} finally {
|
|
1190
|
-
context.isLoading = false;
|
|
1191
|
-
}
|
|
1192
|
-
},
|
|
1193
|
-
async loadBootstrap() {
|
|
1194
|
-
const context = getContext< {{pascalCase}}Context >();
|
|
1195
|
-
const clientState = getClientState( context );
|
|
1196
|
-
if ( context.postId <= 0 || ! context.resourceKey ) {
|
|
1197
|
-
context.bootstrapReady = true;
|
|
1198
|
-
context.canWrite = false;
|
|
1199
|
-
clientState.bootstrapError = '';
|
|
1200
|
-
clientState.writeExpiry = 0;
|
|
1201
|
-
clientState.writeNonce = '';
|
|
1202
|
-
clientState.writeToken = '';
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
context.isBootstrapping = true;
|
|
1207
|
-
|
|
1208
|
-
let bootstrapSucceeded = false;
|
|
1209
|
-
let lastBootstrapError =
|
|
1210
|
-
'Unable to initialize write access';
|
|
1211
|
-
const includePublicWriteCredentials = {{isPublicPersistencePolicy}};
|
|
1212
|
-
const includeRestNonce = {{isAuthenticatedPersistencePolicy}};
|
|
1213
|
-
|
|
1214
|
-
for ( let attempt = 1; attempt <= BOOTSTRAP_MAX_ATTEMPTS; attempt += 1 ) {
|
|
1215
|
-
try {
|
|
1216
|
-
const result = await fetchBootstrap( {
|
|
1217
|
-
postId: context.postId,
|
|
1218
|
-
resourceKey: context.resourceKey,
|
|
1219
|
-
}, {
|
|
1220
|
-
transportTarget: 'frontend',
|
|
1221
|
-
} );
|
|
1222
|
-
if ( ! result.isValid || ! result.data ) {
|
|
1223
|
-
lastBootstrapError =
|
|
1224
|
-
result.errors[ 0 ]?.expected ??
|
|
1225
|
-
'Unable to initialize write access';
|
|
1226
|
-
if ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {
|
|
1227
|
-
await waitForBootstrapRetry(
|
|
1228
|
-
BOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750
|
|
1229
|
-
);
|
|
1230
|
-
continue;
|
|
1231
|
-
}
|
|
1232
|
-
break;
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
clientState.writeExpiry =
|
|
1236
|
-
includePublicWriteCredentials &&
|
|
1237
|
-
'publicWriteExpiresAt' in result.data &&
|
|
1238
|
-
typeof result.data.publicWriteExpiresAt === 'number' &&
|
|
1239
|
-
result.data.publicWriteExpiresAt > 0
|
|
1240
|
-
? result.data.publicWriteExpiresAt
|
|
1241
|
-
: 0;
|
|
1242
|
-
clientState.writeToken =
|
|
1243
|
-
includePublicWriteCredentials &&
|
|
1244
|
-
'publicWriteToken' in result.data &&
|
|
1245
|
-
typeof result.data.publicWriteToken === 'string' &&
|
|
1246
|
-
result.data.publicWriteToken.length > 0
|
|
1247
|
-
? result.data.publicWriteToken
|
|
1248
|
-
: '';
|
|
1249
|
-
clientState.writeNonce =
|
|
1250
|
-
includeRestNonce &&
|
|
1251
|
-
'restNonce' in result.data &&
|
|
1252
|
-
typeof result.data.restNonce === 'string' &&
|
|
1253
|
-
result.data.restNonce.length > 0
|
|
1254
|
-
? result.data.restNonce
|
|
1255
|
-
: '';
|
|
1256
|
-
context.bootstrapReady = true;
|
|
1257
|
-
context.canWrite =
|
|
1258
|
-
result.data.canWrite === true &&
|
|
1259
|
-
(
|
|
1260
|
-
context.persistencePolicy === 'authenticated'
|
|
1261
|
-
? clientState.writeNonce.length > 0
|
|
1262
|
-
: clientState.writeToken.length > 0 &&
|
|
1263
|
-
! hasExpiredPublicWriteToken( clientState.writeExpiry )
|
|
1264
|
-
);
|
|
1265
|
-
clearBootstrapError( context, clientState );
|
|
1266
|
-
bootstrapSucceeded = true;
|
|
1267
|
-
break;
|
|
1268
|
-
} catch ( error ) {
|
|
1269
|
-
lastBootstrapError =
|
|
1270
|
-
error instanceof Error ? error.message : 'Unknown bootstrap error';
|
|
1271
|
-
if ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {
|
|
1272
|
-
await waitForBootstrapRetry(
|
|
1273
|
-
BOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750
|
|
1274
|
-
);
|
|
1275
|
-
continue;
|
|
1276
|
-
}
|
|
1277
|
-
break;
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
if ( ! bootstrapSucceeded ) {
|
|
1282
|
-
context.bootstrapReady = false;
|
|
1283
|
-
context.canWrite = false;
|
|
1284
|
-
clientState.writeExpiry = 0;
|
|
1285
|
-
clientState.writeNonce = '';
|
|
1286
|
-
clientState.writeToken = '';
|
|
1287
|
-
setBootstrapError( context, clientState, lastBootstrapError );
|
|
1288
|
-
}
|
|
1289
|
-
context.isBootstrapping = false;
|
|
1290
|
-
},
|
|
1291
|
-
async increment() {
|
|
1292
|
-
const context = getContext< {{pascalCase}}Context >();
|
|
1293
|
-
const clientState = getClientState( context );
|
|
1294
|
-
if ( context.postId <= 0 || ! context.resourceKey ) {
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
if ( ! context.bootstrapReady ) {
|
|
1298
|
-
await actions.loadBootstrap();
|
|
1299
|
-
}
|
|
1300
|
-
if ( ! context.bootstrapReady ) {
|
|
1301
|
-
context.error = 'Write access is still initializing.';
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
if (
|
|
1305
|
-
context.persistencePolicy === 'public' &&
|
|
1306
|
-
hasExpiredPublicWriteToken( clientState.writeExpiry )
|
|
1307
|
-
) {
|
|
1308
|
-
await actions.loadBootstrap();
|
|
1309
|
-
}
|
|
1310
|
-
if (
|
|
1311
|
-
context.persistencePolicy === 'public' &&
|
|
1312
|
-
hasExpiredPublicWriteToken( clientState.writeExpiry )
|
|
1313
|
-
) {
|
|
1314
|
-
context.canWrite = false;
|
|
1315
|
-
context.error = getWriteBlockedMessage( context );
|
|
1316
|
-
return;
|
|
1317
|
-
}
|
|
1318
|
-
if ( ! context.canWrite ) {
|
|
1319
|
-
context.error = getWriteBlockedMessage( context );
|
|
1320
|
-
return;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
context.isSaving = true;
|
|
1324
|
-
context.error = '';
|
|
1325
|
-
|
|
1326
|
-
try {
|
|
1327
|
-
const request = {
|
|
1328
|
-
delta: 1,
|
|
1329
|
-
postId: context.postId,
|
|
1330
|
-
resourceKey: context.resourceKey,
|
|
1331
|
-
} as {{pascalCase}}WriteStateRequest;
|
|
1332
|
-
if ( {{isPublicPersistencePolicy}} ) {
|
|
1333
|
-
request.publicWriteRequestId =
|
|
1334
|
-
generatePublicWriteRequestId() as {{pascalCase}}WriteStateRequest[ 'publicWriteRequestId' ];
|
|
1335
|
-
if ( clientState.writeToken.length > 0 ) {
|
|
1336
|
-
request.publicWriteToken =
|
|
1337
|
-
clientState.writeToken as {{pascalCase}}WriteStateRequest[ 'publicWriteToken' ];
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
const result = await writeState( request, {
|
|
1341
|
-
restNonce:
|
|
1342
|
-
clientState.writeNonce.length > 0
|
|
1343
|
-
? clientState.writeNonce
|
|
1344
|
-
: undefined,
|
|
1345
|
-
transportTarget: 'frontend',
|
|
1346
|
-
} );
|
|
1347
|
-
if ( ! result.isValid || ! result.data ) {
|
|
1348
|
-
context.error = result.errors[ 0 ]?.expected ?? 'Unable to update counter';
|
|
1349
|
-
return;
|
|
1350
|
-
}
|
|
1351
|
-
context.count = result.data.count;
|
|
1352
|
-
context.storage = result.data.storage;
|
|
1353
|
-
} catch ( error ) {
|
|
1354
|
-
context.error =
|
|
1355
|
-
error instanceof Error ? error.message : 'Unknown update error';
|
|
1356
|
-
} finally {
|
|
1357
|
-
context.isSaving = false;
|
|
1358
|
-
}
|
|
1359
|
-
},
|
|
1360
|
-
},
|
|
1361
|
-
|
|
1362
|
-
callbacks: {
|
|
1363
|
-
init() {
|
|
1364
|
-
const context = getContext< {{pascalCase}}Context >();
|
|
1365
|
-
context.client = {
|
|
1366
|
-
bootstrapError: '',
|
|
1367
|
-
writeExpiry: 0,
|
|
1368
|
-
writeNonce: '',
|
|
1369
|
-
writeToken: '',
|
|
1370
|
-
};
|
|
1371
|
-
context.bootstrapReady = false;
|
|
1372
|
-
context.canWrite = false;
|
|
1373
|
-
context.count = 0;
|
|
1374
|
-
context.error = '';
|
|
1375
|
-
context.isBootstrapping = false;
|
|
1376
|
-
context.isLoading = false;
|
|
1377
|
-
context.isSaving = false;
|
|
1378
|
-
},
|
|
1379
|
-
mounted() {
|
|
1380
|
-
state.isHydrated = true;
|
|
1381
|
-
if ( typeof document !== 'undefined' ) {
|
|
1382
|
-
document.documentElement.dataset[ '{{slugCamelCase}}Hydrated' ] = 'true';
|
|
1383
|
-
}
|
|
1384
|
-
void Promise.allSettled( [
|
|
1385
|
-
actions.loadState(),
|
|
1386
|
-
actions.loadBootstrap(),
|
|
1387
|
-
] );
|
|
1388
|
-
},
|
|
1389
|
-
},
|
|
1390
|
-
} );
|
|
1391
|
-
`;
|
|
1392
|
-
export const COMPOUND_PARENT_EDIT_TEMPLATE = `import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';
|
|
1393
|
-
import { __ } from '@wordpress/i18n';
|
|
1394
|
-
import {
|
|
1395
|
-
InspectorControls,
|
|
1396
|
-
InnerBlocks,
|
|
1397
|
-
RichText,
|
|
1398
|
-
useBlockProps,
|
|
1399
|
-
} from '@wordpress/block-editor';
|
|
1400
|
-
import { Notice, PanelBody, ToggleControl } from '@wordpress/components';
|
|
1401
|
-
|
|
1402
|
-
import {
|
|
1403
|
-
ALLOWED_CHILD_BLOCKS,
|
|
1404
|
-
DEFAULT_CHILD_TEMPLATE,
|
|
1405
|
-
} from './children';
|
|
1406
|
-
import { useTypiaValidation } from './hooks';
|
|
1407
|
-
import type { {{pascalCase}}Attributes } from './types';
|
|
1408
|
-
import {
|
|
1409
|
-
createAttributeUpdater,
|
|
1410
|
-
validate{{pascalCase}}Attributes,
|
|
1411
|
-
} from './validators';
|
|
1412
|
-
|
|
1413
|
-
type EditProps = BlockEditProps< {{pascalCase}}Attributes >;
|
|
1414
|
-
|
|
1415
|
-
export default function Edit( {
|
|
1416
|
-
attributes,
|
|
1417
|
-
setAttributes,
|
|
1418
|
-
}: EditProps ) {
|
|
1419
|
-
const { errorMessages, isValid } = useTypiaValidation(
|
|
1420
|
-
attributes,
|
|
1421
|
-
validate{{pascalCase}}Attributes
|
|
1422
|
-
);
|
|
1423
|
-
const updateAttribute = createAttributeUpdater( attributes, setAttributes );
|
|
1424
|
-
const blockProps = useBlockProps( {
|
|
1425
|
-
className: '{{cssClassName}}',
|
|
1426
|
-
} );
|
|
1427
|
-
|
|
1428
|
-
return (
|
|
1429
|
-
<>
|
|
1430
|
-
<InspectorControls>
|
|
1431
|
-
<PanelBody title={ __( 'Compound Settings', '{{textDomain}}' ) }>
|
|
1432
|
-
<ToggleControl
|
|
1433
|
-
label={ __( 'Show dividers between items', '{{textDomain}}' ) }
|
|
1434
|
-
checked={ attributes.showDividers ?? true }
|
|
1435
|
-
onChange={ ( value ) => updateAttribute( 'showDividers', value ) }
|
|
1436
|
-
/>
|
|
1437
|
-
</PanelBody>
|
|
1438
|
-
{ ! isValid && (
|
|
1439
|
-
<PanelBody title={ __( 'Validation Errors', '{{textDomain}}' ) } initialOpen>
|
|
1440
|
-
{ errorMessages.map( ( error, index ) => (
|
|
1441
|
-
<Notice key={ index } status="error" isDismissible={ false }>
|
|
1442
|
-
{ error }
|
|
1443
|
-
</Notice>
|
|
1444
|
-
) ) }
|
|
1445
|
-
</PanelBody>
|
|
1446
|
-
) }
|
|
1447
|
-
</InspectorControls>
|
|
1448
|
-
<div { ...blockProps }>
|
|
1449
|
-
<RichText
|
|
1450
|
-
tagName="h3"
|
|
1451
|
-
className="{{cssClassName}}__heading"
|
|
1452
|
-
value={ attributes.heading }
|
|
1453
|
-
onChange={ ( heading ) => updateAttribute( 'heading', heading ) }
|
|
1454
|
-
placeholder={ __( {{titleJson}}, '{{textDomain}}' ) }
|
|
1455
|
-
/>
|
|
1456
|
-
<RichText
|
|
1457
|
-
tagName="p"
|
|
1458
|
-
className="{{cssClassName}}__intro"
|
|
1459
|
-
value={ attributes.intro ?? '' }
|
|
1460
|
-
onChange={ ( intro ) => updateAttribute( 'intro', intro ) }
|
|
1461
|
-
placeholder={ __(
|
|
1462
|
-
'Add and reorder internal items inside this compound block.',
|
|
1463
|
-
'{{textDomain}}'
|
|
1464
|
-
) }
|
|
1465
|
-
/>
|
|
1466
|
-
{ ! isValid && (
|
|
1467
|
-
<Notice status="error" isDismissible={ false }>
|
|
1468
|
-
<ul>
|
|
1469
|
-
{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }
|
|
1470
|
-
</ul>
|
|
1471
|
-
</Notice>
|
|
1472
|
-
) }
|
|
1473
|
-
<div className="{{cssClassName}}__items">
|
|
1474
|
-
<InnerBlocks
|
|
1475
|
-
allowedBlocks={ ALLOWED_CHILD_BLOCKS }
|
|
1476
|
-
renderAppender={ InnerBlocks.ButtonBlockAppender }
|
|
1477
|
-
template={ DEFAULT_CHILD_TEMPLATE }
|
|
1478
|
-
templateLock={ false }
|
|
1479
|
-
/>
|
|
1480
|
-
</div>
|
|
1481
|
-
</div>
|
|
1482
|
-
</>
|
|
1483
|
-
);
|
|
1484
|
-
}
|
|
1485
|
-
`;
|
|
1486
|
-
export const COMPOUND_PARENT_SAVE_TEMPLATE = `import { InnerBlocks, RichText, useBlockProps } from '@wordpress/block-editor';
|
|
1487
|
-
|
|
1488
|
-
import type { {{pascalCase}}Attributes } from './types';
|
|
1489
|
-
|
|
1490
|
-
export default function Save( {
|
|
1491
|
-
attributes,
|
|
1492
|
-
}: {
|
|
1493
|
-
attributes: {{pascalCase}}Attributes;
|
|
1494
|
-
} ) {
|
|
1495
|
-
return (
|
|
1496
|
-
<div
|
|
1497
|
-
{ ...useBlockProps.save( {
|
|
1498
|
-
className: '{{cssClassName}}',
|
|
1499
|
-
'data-show-dividers': ( attributes.showDividers ?? true ) ? 'true' : 'false',
|
|
1500
|
-
} ) }
|
|
1501
|
-
>
|
|
1502
|
-
<RichText.Content
|
|
1503
|
-
tagName="h3"
|
|
1504
|
-
className="{{cssClassName}}__heading"
|
|
1505
|
-
value={ attributes.heading }
|
|
1506
|
-
/>
|
|
1507
|
-
<RichText.Content
|
|
1508
|
-
tagName="p"
|
|
1509
|
-
className="{{cssClassName}}__intro"
|
|
1510
|
-
value={ attributes.intro ?? '' }
|
|
1511
|
-
/>
|
|
1512
|
-
<div className="{{cssClassName}}__items">
|
|
1513
|
-
<InnerBlocks.Content />
|
|
1514
|
-
</div>
|
|
1515
|
-
</div>
|
|
1516
|
-
);
|
|
1517
|
-
}
|
|
1518
|
-
`;
|
|
1519
|
-
export const COMPOUND_PARENT_INDEX_TEMPLATE = `import {
|
|
1520
|
-
\tregisterScaffoldBlockType,
|
|
1521
|
-
\ttype BlockConfiguration,
|
|
1522
|
-
} from '@wp-typia/block-types/blocks/registration';
|
|
1523
|
-
import {
|
|
1524
|
-
buildScaffoldBlockRegistration,
|
|
1525
|
-
parseScaffoldBlockMetadata,
|
|
1526
|
-
} from '@wp-typia/block-runtime/blocks';
|
|
1527
|
-
|
|
1528
|
-
import Edit from './edit';
|
|
1529
|
-
import Save from './save';
|
|
1530
|
-
import metadata from './block-metadata';
|
|
1531
|
-
import './style.scss';
|
|
1532
|
-
|
|
1533
|
-
import type { {{pascalCase}}Attributes } from './types';
|
|
1534
|
-
|
|
1535
|
-
const registration = buildScaffoldBlockRegistration(
|
|
1536
|
-
parseScaffoldBlockMetadata<BlockConfiguration< {{pascalCase}}Attributes >>( metadata ),
|
|
1537
|
-
{
|
|
1538
|
-
edit: Edit,
|
|
1539
|
-
save: Save,
|
|
1540
|
-
}
|
|
1541
|
-
);
|
|
1542
|
-
|
|
1543
|
-
registerScaffoldBlockType(registration.name, registration.settings);
|
|
1544
|
-
`;
|
|
1545
|
-
export const COMPOUND_LOCAL_HOOKS_TEMPLATE = `export {
|
|
1546
|
-
formatValidationError,
|
|
1547
|
-
formatValidationErrors,
|
|
1548
|
-
useTypiaValidation,
|
|
1549
|
-
} from '../../hooks';
|
|
1550
|
-
|
|
1551
|
-
export type {
|
|
1552
|
-
TypiaValidationError,
|
|
1553
|
-
ValidationResult,
|
|
1554
|
-
ValidationState,
|
|
1555
|
-
} from '../../hooks';
|
|
1556
|
-
`;
|
|
1557
|
-
export const COMPOUND_PARENT_VALIDATORS_TEMPLATE = `import typia from 'typia';
|
|
1558
|
-
import currentManifest from './manifest-defaults-document';
|
|
1559
|
-
import type {
|
|
1560
|
-
{{pascalCase}}Attributes,
|
|
1561
|
-
{{pascalCase}}ValidationResult,
|
|
1562
|
-
} from './types';
|
|
1563
|
-
import { createTemplateValidatorToolkit } from '../../validator-toolkit';
|
|
1564
|
-
|
|
1565
|
-
const scaffoldValidators = createTemplateValidatorToolkit< {{pascalCase}}Attributes >( {
|
|
1566
|
-
assert: typia.createAssert< {{pascalCase}}Attributes >(),
|
|
1567
|
-
clone: typia.misc.createClone< {{pascalCase}}Attributes >() as (
|
|
1568
|
-
value: {{pascalCase}}Attributes,
|
|
1569
|
-
) => {{pascalCase}}Attributes,
|
|
1570
|
-
is: typia.createIs< {{pascalCase}}Attributes >(),
|
|
1571
|
-
manifest: currentManifest,
|
|
1572
|
-
prune: typia.misc.createPrune< {{pascalCase}}Attributes >(),
|
|
1573
|
-
random: typia.createRandom< {{pascalCase}}Attributes >() as (
|
|
1574
|
-
...args: unknown[]
|
|
1575
|
-
) => {{pascalCase}}Attributes,
|
|
1576
|
-
validate: typia.createValidate< {{pascalCase}}Attributes >(),
|
|
1577
|
-
} );
|
|
1578
|
-
|
|
1579
|
-
export const validate{{pascalCase}}Attributes =
|
|
1580
|
-
scaffoldValidators.validateAttributes as (
|
|
1581
|
-
attributes: unknown
|
|
1582
|
-
) => {{pascalCase}}ValidationResult;
|
|
1583
|
-
|
|
1584
|
-
export const validators = scaffoldValidators.validators;
|
|
1585
|
-
|
|
1586
|
-
export const sanitize{{pascalCase}}Attributes =
|
|
1587
|
-
scaffoldValidators.sanitizeAttributes as (
|
|
1588
|
-
attributes: Partial< {{pascalCase}}Attributes >
|
|
1589
|
-
) => {{pascalCase}}Attributes;
|
|
1590
|
-
|
|
1591
|
-
export const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;
|
|
1592
|
-
`;
|
|
1593
|
-
export const COMPOUND_CHILDREN_TEMPLATE = `import type { BlockTemplate } from '@wp-typia/block-types/blocks/registration';
|
|
1594
|
-
|
|
1595
|
-
export const DEFAULT_CHILD_BLOCK_NAME = '{{namespace}}/{{slugKebabCase}}-item';
|
|
1596
|
-
|
|
1597
|
-
export const ALLOWED_CHILD_BLOCKS = [
|
|
1598
|
-
DEFAULT_CHILD_BLOCK_NAME,
|
|
1599
|
-
// add-child: insert new allowed child block names here
|
|
1600
|
-
];
|
|
1601
|
-
|
|
1602
|
-
export const DEFAULT_CHILD_TEMPLATE: BlockTemplate = [
|
|
1603
|
-
[
|
|
1604
|
-
DEFAULT_CHILD_BLOCK_NAME,
|
|
1605
|
-
{
|
|
1606
|
-
body: 'Add supporting details for the first internal item.',
|
|
1607
|
-
title: 'First Item',
|
|
1608
|
-
},
|
|
1609
|
-
],
|
|
1610
|
-
[
|
|
1611
|
-
DEFAULT_CHILD_BLOCK_NAME,
|
|
1612
|
-
{
|
|
1613
|
-
body: 'Add supporting details for the second internal item.',
|
|
1614
|
-
title: 'Second Item',
|
|
1615
|
-
},
|
|
1616
|
-
],
|
|
1617
|
-
];
|
|
1618
|
-
`;
|
|
1619
|
-
export const COMPOUND_CHILD_EDIT_TEMPLATE = `import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';
|
|
1620
|
-
import { RichText, useBlockProps } from '@wordpress/block-editor';
|
|
1621
|
-
import { Notice } from '@wordpress/components';
|
|
1622
|
-
import { __ } from '@wordpress/i18n';
|
|
1623
|
-
|
|
1624
|
-
import { useTypiaValidation } from './hooks';
|
|
1625
|
-
import type { {{pascalCase}}ItemAttributes } from './types';
|
|
1626
|
-
import {
|
|
1627
|
-
createAttributeUpdater,
|
|
1628
|
-
validate{{pascalCase}}ItemAttributes,
|
|
1629
|
-
} from './validators';
|
|
1630
|
-
|
|
1631
|
-
type EditProps = BlockEditProps< {{pascalCase}}ItemAttributes >;
|
|
1632
|
-
|
|
1633
|
-
export default function Edit( {
|
|
1634
|
-
attributes,
|
|
1635
|
-
setAttributes,
|
|
1636
|
-
}: EditProps ) {
|
|
1637
|
-
const updateAttribute = createAttributeUpdater( attributes, setAttributes );
|
|
1638
|
-
const { errorMessages, isValid } = useTypiaValidation(
|
|
1639
|
-
attributes,
|
|
1640
|
-
validate{{pascalCase}}ItemAttributes
|
|
1641
|
-
);
|
|
1642
|
-
|
|
1643
|
-
return (
|
|
1644
|
-
<div { ...useBlockProps( { className: '{{compoundChildCssClassName}}' } ) }>
|
|
1645
|
-
<RichText
|
|
1646
|
-
tagName="h4"
|
|
1647
|
-
className="{{compoundChildCssClassName}}__title"
|
|
1648
|
-
value={ attributes.title ?? '' }
|
|
1649
|
-
onChange={ ( title ) => updateAttribute( 'title', title ) }
|
|
1650
|
-
placeholder={ __( {{compoundChildTitleJson}}, '{{textDomain}}' ) }
|
|
1651
|
-
/>
|
|
1652
|
-
<RichText
|
|
1653
|
-
tagName="p"
|
|
1654
|
-
className="{{compoundChildCssClassName}}__body"
|
|
1655
|
-
value={ attributes.body ?? '' }
|
|
1656
|
-
onChange={ ( body ) => updateAttribute( 'body', body ) }
|
|
1657
|
-
placeholder={ __( 'Add supporting details for this internal item.', '{{textDomain}}' ) }
|
|
1658
|
-
/>
|
|
1659
|
-
{ ! isValid && (
|
|
1660
|
-
<Notice status="error" isDismissible={ false }>
|
|
1661
|
-
<ul>
|
|
1662
|
-
{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }
|
|
1663
|
-
</ul>
|
|
1664
|
-
</Notice>
|
|
1665
|
-
) }
|
|
1666
|
-
</div>
|
|
1667
|
-
);
|
|
1668
|
-
}
|
|
1669
|
-
`;
|
|
1670
|
-
export const COMPOUND_CHILD_SAVE_TEMPLATE = `import { RichText, useBlockProps } from '@wordpress/block-editor';
|
|
1671
|
-
|
|
1672
|
-
import type { {{pascalCase}}ItemAttributes } from './types';
|
|
1673
|
-
|
|
1674
|
-
export default function Save( {
|
|
1675
|
-
attributes,
|
|
1676
|
-
}: {
|
|
1677
|
-
attributes: {{pascalCase}}ItemAttributes;
|
|
1678
|
-
} ) {
|
|
1679
|
-
return (
|
|
1680
|
-
<div { ...useBlockProps.save( { className: '{{compoundChildCssClassName}}' } ) }>
|
|
1681
|
-
<RichText.Content
|
|
1682
|
-
tagName="h4"
|
|
1683
|
-
className="{{compoundChildCssClassName}}__title"
|
|
1684
|
-
value={ attributes.title }
|
|
1685
|
-
/>
|
|
1686
|
-
<RichText.Content
|
|
1687
|
-
tagName="p"
|
|
1688
|
-
className="{{compoundChildCssClassName}}__body"
|
|
1689
|
-
value={ attributes.body }
|
|
1690
|
-
/>
|
|
1691
|
-
</div>
|
|
1692
|
-
);
|
|
1693
|
-
}
|
|
1694
|
-
`;
|
|
1695
|
-
export const COMPOUND_CHILD_INDEX_TEMPLATE = `import {
|
|
1696
|
-
\tregisterScaffoldBlockType,
|
|
1697
|
-
\ttype BlockConfiguration,
|
|
1698
|
-
} from '@wp-typia/block-types/blocks/registration';
|
|
1699
|
-
import {
|
|
1700
|
-
buildScaffoldBlockRegistration,
|
|
1701
|
-
parseScaffoldBlockMetadata,
|
|
1702
|
-
} from '@wp-typia/block-runtime/blocks';
|
|
1703
|
-
|
|
1704
|
-
import Edit from './edit';
|
|
1705
|
-
import Save from './save';
|
|
1706
|
-
import metadata from './block-metadata';
|
|
1707
|
-
import '../{{slugKebabCase}}/style.scss';
|
|
1708
|
-
|
|
1709
|
-
import type { {{pascalCase}}ItemAttributes } from './types';
|
|
1710
|
-
|
|
1711
|
-
const registration = buildScaffoldBlockRegistration(
|
|
1712
|
-
parseScaffoldBlockMetadata<BlockConfiguration< {{pascalCase}}ItemAttributes >>( metadata ),
|
|
1713
|
-
{
|
|
1714
|
-
edit: Edit,
|
|
1715
|
-
save: Save,
|
|
1716
|
-
}
|
|
1717
|
-
);
|
|
1718
|
-
|
|
1719
|
-
registerScaffoldBlockType(registration.name, registration.settings);
|
|
1720
|
-
`;
|
|
1721
|
-
export const COMPOUND_CHILD_VALIDATORS_TEMPLATE = `import typia from 'typia';
|
|
1722
|
-
import currentManifest from './manifest-defaults-document';
|
|
1723
|
-
import type {
|
|
1724
|
-
{{pascalCase}}ItemAttributes,
|
|
1725
|
-
{{pascalCase}}ItemValidationResult,
|
|
1726
|
-
} from './types';
|
|
1727
|
-
import { createTemplateValidatorToolkit } from '../../validator-toolkit';
|
|
1728
|
-
|
|
1729
|
-
const scaffoldValidators = createTemplateValidatorToolkit< {{pascalCase}}ItemAttributes >( {
|
|
1730
|
-
assert: typia.createAssert< {{pascalCase}}ItemAttributes >(),
|
|
1731
|
-
clone: typia.misc.createClone< {{pascalCase}}ItemAttributes >() as (
|
|
1732
|
-
value: {{pascalCase}}ItemAttributes,
|
|
1733
|
-
) => {{pascalCase}}ItemAttributes,
|
|
1734
|
-
is: typia.createIs< {{pascalCase}}ItemAttributes >(),
|
|
1735
|
-
manifest: currentManifest,
|
|
1736
|
-
prune: typia.misc.createPrune< {{pascalCase}}ItemAttributes >(),
|
|
1737
|
-
random: typia.createRandom< {{pascalCase}}ItemAttributes >() as (
|
|
1738
|
-
...args: unknown[]
|
|
1739
|
-
) => {{pascalCase}}ItemAttributes,
|
|
1740
|
-
validate: typia.createValidate< {{pascalCase}}ItemAttributes >(),
|
|
1741
|
-
} );
|
|
1742
|
-
|
|
1743
|
-
export const validate{{pascalCase}}ItemAttributes =
|
|
1744
|
-
scaffoldValidators.validateAttributes as (
|
|
1745
|
-
attributes: unknown
|
|
1746
|
-
) => {{pascalCase}}ItemValidationResult;
|
|
1747
|
-
|
|
1748
|
-
export const validators = scaffoldValidators.validators;
|
|
1749
|
-
|
|
1750
|
-
export const sanitize{{pascalCase}}ItemAttributes =
|
|
1751
|
-
scaffoldValidators.sanitizeAttributes as (
|
|
1752
|
-
attributes: Partial< {{pascalCase}}ItemAttributes >
|
|
1753
|
-
) => {{pascalCase}}ItemAttributes;
|
|
1754
|
-
|
|
1755
|
-
export const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;
|
|
1756
|
-
`;
|
|
1757
|
-
export const COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE = `import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';
|
|
1758
|
-
import { __ } from '@wordpress/i18n';
|
|
1759
|
-
import {
|
|
1760
|
-
InspectorControls,
|
|
1761
|
-
InnerBlocks,
|
|
1762
|
-
RichText,
|
|
1763
|
-
useBlockProps,
|
|
1764
|
-
} from '@wordpress/block-editor';
|
|
1765
|
-
import {
|
|
1766
|
-
Notice,
|
|
1767
|
-
PanelBody,
|
|
1768
|
-
TextControl,
|
|
1769
|
-
ToggleControl,
|
|
1770
|
-
} from '@wordpress/components';
|
|
1771
|
-
|
|
1772
|
-
import {
|
|
1773
|
-
ALLOWED_CHILD_BLOCKS,
|
|
1774
|
-
DEFAULT_CHILD_TEMPLATE,
|
|
1775
|
-
} from './children';
|
|
1776
|
-
import { useTypiaValidation } from './hooks';
|
|
1777
|
-
import type { {{pascalCase}}Attributes } from './types';
|
|
1778
|
-
import {
|
|
1779
|
-
createAttributeUpdater,
|
|
1780
|
-
validate{{pascalCase}}Attributes,
|
|
1781
|
-
} from './validators';
|
|
1782
|
-
|
|
1783
|
-
type EditProps = BlockEditProps< {{pascalCase}}Attributes >;
|
|
1784
|
-
|
|
1785
|
-
export default function Edit( {
|
|
1786
|
-
attributes,
|
|
1787
|
-
setAttributes,
|
|
1788
|
-
}: EditProps ) {
|
|
1789
|
-
const { errorMessages, isValid } = useTypiaValidation(
|
|
1790
|
-
attributes,
|
|
1791
|
-
validate{{pascalCase}}Attributes
|
|
1792
|
-
);
|
|
1793
|
-
const updateAttribute = createAttributeUpdater( attributes, setAttributes );
|
|
1794
|
-
const blockProps = useBlockProps( {
|
|
1795
|
-
className: '{{cssClassName}}',
|
|
1796
|
-
} );
|
|
1797
|
-
|
|
1798
|
-
return (
|
|
1799
|
-
<>
|
|
1800
|
-
<InspectorControls>
|
|
1801
|
-
<PanelBody title={ __( 'Compound Settings', '{{textDomain}}' ) }>
|
|
1802
|
-
<ToggleControl
|
|
1803
|
-
label={ __( 'Show dividers between items', '{{textDomain}}' ) }
|
|
1804
|
-
checked={ attributes.showDividers ?? true }
|
|
1805
|
-
onChange={ ( value ) => updateAttribute( 'showDividers', value ) }
|
|
1806
|
-
/>
|
|
1807
|
-
<ToggleControl
|
|
1808
|
-
label={ __( 'Show persisted count', '{{textDomain}}' ) }
|
|
1809
|
-
checked={ attributes.showCount ?? true }
|
|
1810
|
-
onChange={ ( value ) => updateAttribute( 'showCount', value ) }
|
|
1811
|
-
/>
|
|
1812
|
-
<TextControl
|
|
1813
|
-
label={ __( 'Button label', '{{textDomain}}' ) }
|
|
1814
|
-
value={ attributes.buttonLabel ?? 'Persist Count' }
|
|
1815
|
-
onChange={ ( buttonLabel ) => updateAttribute( 'buttonLabel', buttonLabel ) }
|
|
1816
|
-
/>
|
|
1817
|
-
<TextControl
|
|
1818
|
-
label={ __( 'Resource key', '{{textDomain}}' ) }
|
|
1819
|
-
value={ attributes.resourceKey ?? '' }
|
|
1820
|
-
onChange={ ( resourceKey ) => updateAttribute( 'resourceKey', resourceKey ) }
|
|
1821
|
-
help={ __( 'Stable key used by the persisted counter endpoint.', '{{textDomain}}' ) }
|
|
1822
|
-
/>
|
|
1823
|
-
<Notice status="info" isDismissible={ false }>
|
|
1824
|
-
{ __( 'Storage mode: {{dataStorageMode}}', '{{textDomain}}' ) }
|
|
1825
|
-
</Notice>
|
|
1826
|
-
<Notice status="info" isDismissible={ false }>
|
|
1827
|
-
{ __( 'Persistence policy: {{persistencePolicy}}', '{{textDomain}}' ) }
|
|
1828
|
-
</Notice>
|
|
1829
|
-
</PanelBody>
|
|
1830
|
-
{ ! isValid && (
|
|
1831
|
-
<PanelBody title={ __( 'Validation Errors', '{{textDomain}}' ) } initialOpen>
|
|
1832
|
-
{ errorMessages.map( ( error, index ) => (
|
|
1833
|
-
<Notice key={ index } status="error" isDismissible={ false }>
|
|
1834
|
-
{ error }
|
|
1835
|
-
</Notice>
|
|
1836
|
-
) ) }
|
|
1837
|
-
</PanelBody>
|
|
1838
|
-
) }
|
|
1839
|
-
</InspectorControls>
|
|
1840
|
-
<div { ...blockProps }>
|
|
1841
|
-
<RichText
|
|
1842
|
-
tagName="h3"
|
|
1843
|
-
className="{{cssClassName}}__heading"
|
|
1844
|
-
value={ attributes.heading }
|
|
1845
|
-
onChange={ ( heading ) => updateAttribute( 'heading', heading ) }
|
|
1846
|
-
placeholder={ __( {{titleJson}}, '{{textDomain}}' ) }
|
|
1847
|
-
/>
|
|
1848
|
-
<RichText
|
|
1849
|
-
tagName="p"
|
|
1850
|
-
className="{{cssClassName}}__intro"
|
|
1851
|
-
value={ attributes.intro ?? '' }
|
|
1852
|
-
onChange={ ( intro ) => updateAttribute( 'intro', intro ) }
|
|
1853
|
-
placeholder={ __(
|
|
1854
|
-
'Add and reorder internal items inside this compound block.',
|
|
1855
|
-
'{{textDomain}}'
|
|
1856
|
-
) }
|
|
1857
|
-
/>
|
|
1858
|
-
{ ! isValid && (
|
|
1859
|
-
<Notice status="error" isDismissible={ false }>
|
|
1860
|
-
<ul>
|
|
1861
|
-
{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }
|
|
1862
|
-
</ul>
|
|
1863
|
-
</Notice>
|
|
1864
|
-
) }
|
|
1865
|
-
<p className="{{cssClassName}}__meta">
|
|
1866
|
-
{ __( 'Resource key:', '{{textDomain}}' ) } { attributes.resourceKey || '—' }
|
|
1867
|
-
</p>
|
|
1868
|
-
<div className="{{cssClassName}}__items">
|
|
1869
|
-
<InnerBlocks
|
|
1870
|
-
allowedBlocks={ ALLOWED_CHILD_BLOCKS }
|
|
1871
|
-
renderAppender={ InnerBlocks.ButtonBlockAppender }
|
|
1872
|
-
template={ DEFAULT_CHILD_TEMPLATE }
|
|
1873
|
-
templateLock={ false }
|
|
1874
|
-
/>
|
|
1875
|
-
</div>
|
|
1876
|
-
</div>
|
|
1877
|
-
</>
|
|
1878
|
-
);
|
|
1879
|
-
}
|
|
1880
|
-
`;
|
|
1881
|
-
export const COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE = `export default function Save() {
|
|
1882
|
-
return null;
|
|
1883
|
-
}
|
|
1884
|
-
`;
|
|
1885
|
-
export const COMPOUND_PERSISTENCE_PARENT_VALIDATORS_TEMPLATE = `import typia from 'typia';
|
|
1886
|
-
import currentManifest from './manifest-defaults-document';
|
|
1887
|
-
import type {
|
|
1888
|
-
{{pascalCase}}Attributes,
|
|
1889
|
-
{{pascalCase}}ValidationResult,
|
|
1890
|
-
} from './types';
|
|
1891
|
-
import { generateResourceKey } from '@wp-typia/block-runtime/identifiers';
|
|
1892
|
-
import { createTemplateValidatorToolkit } from '../../validator-toolkit';
|
|
1893
|
-
|
|
1894
|
-
const scaffoldValidators = createTemplateValidatorToolkit< {{pascalCase}}Attributes >( {
|
|
1895
|
-
assert: typia.createAssert< {{pascalCase}}Attributes >(),
|
|
1896
|
-
clone: typia.misc.createClone< {{pascalCase}}Attributes >() as (
|
|
1897
|
-
value: {{pascalCase}}Attributes,
|
|
1898
|
-
) => {{pascalCase}}Attributes,
|
|
1899
|
-
is: typia.createIs< {{pascalCase}}Attributes >(),
|
|
1900
|
-
manifest: currentManifest,
|
|
1901
|
-
prune: typia.misc.createPrune< {{pascalCase}}Attributes >(),
|
|
1902
|
-
random: typia.createRandom< {{pascalCase}}Attributes >() as (
|
|
1903
|
-
...args: unknown[]
|
|
1904
|
-
) => {{pascalCase}}Attributes,
|
|
1905
|
-
finalize: ( normalized ) => ( {
|
|
1906
|
-
...normalized,
|
|
1907
|
-
resourceKey:
|
|
1908
|
-
normalized.resourceKey && normalized.resourceKey.length > 0
|
|
1909
|
-
? normalized.resourceKey
|
|
1910
|
-
: generateResourceKey( '{{slugKebabCase}}' ),
|
|
1911
|
-
} ),
|
|
1912
|
-
validate: typia.createValidate< {{pascalCase}}Attributes >(),
|
|
1913
|
-
} );
|
|
1914
|
-
|
|
1915
|
-
export const validators = scaffoldValidators.validators;
|
|
1916
|
-
|
|
1917
|
-
export const validate{{pascalCase}}Attributes =
|
|
1918
|
-
scaffoldValidators.validateAttributes as (
|
|
1919
|
-
attributes: unknown
|
|
1920
|
-
) => {{pascalCase}}ValidationResult;
|
|
1921
|
-
|
|
1922
|
-
export const sanitize{{pascalCase}}Attributes =
|
|
1923
|
-
scaffoldValidators.sanitizeAttributes as (
|
|
1924
|
-
attributes: Partial< {{pascalCase}}Attributes >
|
|
1925
|
-
) => {{pascalCase}}Attributes;
|
|
1926
|
-
|
|
1927
|
-
export const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;
|
|
1928
|
-
`;
|
|
1929
|
-
export const COMPOUND_PERSISTENCE_PARENT_INTERACTIVITY_TEMPLATE = `import { getContext, store } from '@wordpress/interactivity';
|
|
1930
|
-
import { generatePublicWriteRequestId } from '@wp-typia/block-runtime/identifiers';
|
|
1931
|
-
|
|
1932
|
-
import { fetchBootstrap, fetchState, writeState } from './api';
|
|
1933
|
-
import type {
|
|
1934
|
-
{{pascalCase}}ClientState,
|
|
1935
|
-
{{pascalCase}}Context,
|
|
1936
|
-
{{pascalCase}}State,
|
|
1937
|
-
} from './types';
|
|
1938
|
-
|
|
1939
|
-
function hasExpiredPublicWriteToken(
|
|
1940
|
-
expiresAt?: number
|
|
1941
|
-
): boolean {
|
|
1942
|
-
return (
|
|
1943
|
-
typeof expiresAt === 'number' &&
|
|
1944
|
-
expiresAt > 0 &&
|
|
1945
|
-
Date.now() >= expiresAt * 1000
|
|
1946
|
-
);
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
|
-
function getWriteBlockedMessage(
|
|
1950
|
-
context: {{pascalCase}}Context
|
|
1951
|
-
): string {
|
|
1952
|
-
return context.persistencePolicy === 'authenticated'
|
|
1953
|
-
? 'Sign in to persist this counter.'
|
|
1954
|
-
: 'Public writes are temporarily unavailable.';
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
const BOOTSTRAP_MAX_ATTEMPTS = 3;
|
|
1958
|
-
const BOOTSTRAP_RETRY_DELAYS_MS = [ 250, 500 ];
|
|
1959
|
-
|
|
1960
|
-
async function waitForBootstrapRetry( delayMs: number ): Promise< void > {
|
|
1961
|
-
await new Promise( ( resolve ) => {
|
|
1962
|
-
setTimeout( resolve, delayMs );
|
|
1963
|
-
} );
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
|
-
function getClientState(
|
|
1967
|
-
context: {{pascalCase}}Context
|
|
1968
|
-
): {{pascalCase}}ClientState {
|
|
1969
|
-
if ( context.client ) {
|
|
1970
|
-
return context.client;
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
context.client = {
|
|
1974
|
-
bootstrapError: '',
|
|
1975
|
-
writeExpiry: 0,
|
|
1976
|
-
writeNonce: '',
|
|
1977
|
-
writeToken: '',
|
|
1978
|
-
};
|
|
1979
|
-
|
|
1980
|
-
return context.client;
|
|
1981
|
-
}
|
|
1982
|
-
|
|
1983
|
-
function clearBootstrapError(
|
|
1984
|
-
context: {{pascalCase}}Context,
|
|
1985
|
-
clientState: {{pascalCase}}ClientState
|
|
1986
|
-
): void {
|
|
1987
|
-
if ( context.error === clientState.bootstrapError ) {
|
|
1988
|
-
context.error = '';
|
|
1989
|
-
}
|
|
1990
|
-
clientState.bootstrapError = '';
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
function setBootstrapError(
|
|
1994
|
-
context: {{pascalCase}}Context,
|
|
1995
|
-
clientState: {{pascalCase}}ClientState,
|
|
1996
|
-
message: string
|
|
1997
|
-
): void {
|
|
1998
|
-
clientState.bootstrapError = message;
|
|
1999
|
-
context.error = message;
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
const { actions, state } = store( '{{slugKebabCase}}', {
|
|
2003
|
-
state: {
|
|
2004
|
-
isHydrated: false,
|
|
2005
|
-
} as {{pascalCase}}State,
|
|
2006
|
-
|
|
2007
|
-
actions: {
|
|
2008
|
-
async loadState() {
|
|
2009
|
-
const context = getContext< {{pascalCase}}Context >();
|
|
2010
|
-
if ( context.postId <= 0 || ! context.resourceKey ) {
|
|
2011
|
-
return;
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
context.isLoading = true;
|
|
2015
|
-
context.error = '';
|
|
2016
|
-
|
|
2017
|
-
try {
|
|
2018
|
-
const result = await fetchState( {
|
|
2019
|
-
postId: context.postId,
|
|
2020
|
-
resourceKey: context.resourceKey,
|
|
2021
|
-
}, {
|
|
2022
|
-
transportTarget: 'frontend',
|
|
2023
|
-
} );
|
|
2024
|
-
if ( ! result.isValid || ! result.data ) {
|
|
2025
|
-
context.error = result.errors[ 0 ]?.expected ?? 'Unable to load counter';
|
|
2026
|
-
return;
|
|
2027
|
-
}
|
|
2028
|
-
context.count = result.data.count;
|
|
2029
|
-
} catch ( error ) {
|
|
2030
|
-
context.error =
|
|
2031
|
-
error instanceof Error ? error.message : 'Unknown loading error';
|
|
2032
|
-
} finally {
|
|
2033
|
-
context.isLoading = false;
|
|
2034
|
-
}
|
|
2035
|
-
},
|
|
2036
|
-
async loadBootstrap() {
|
|
2037
|
-
const context = getContext< {{pascalCase}}Context >();
|
|
2038
|
-
const clientState = getClientState( context );
|
|
2039
|
-
if ( context.postId <= 0 || ! context.resourceKey ) {
|
|
2040
|
-
context.bootstrapReady = true;
|
|
2041
|
-
context.canWrite = false;
|
|
2042
|
-
clientState.bootstrapError = '';
|
|
2043
|
-
clientState.writeExpiry = 0;
|
|
2044
|
-
clientState.writeNonce = '';
|
|
2045
|
-
clientState.writeToken = '';
|
|
2046
|
-
return;
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
context.isBootstrapping = true;
|
|
2050
|
-
|
|
2051
|
-
let bootstrapSucceeded = false;
|
|
2052
|
-
let lastBootstrapError =
|
|
2053
|
-
'Unable to initialize write access';
|
|
2054
|
-
const includePublicWriteCredentials = {{isPublicPersistencePolicy}};
|
|
2055
|
-
const includeRestNonce = {{isAuthenticatedPersistencePolicy}};
|
|
2056
|
-
|
|
2057
|
-
for ( let attempt = 1; attempt <= BOOTSTRAP_MAX_ATTEMPTS; attempt += 1 ) {
|
|
2058
|
-
try {
|
|
2059
|
-
const result = await fetchBootstrap( {
|
|
2060
|
-
postId: context.postId,
|
|
2061
|
-
resourceKey: context.resourceKey,
|
|
2062
|
-
}, {
|
|
2063
|
-
transportTarget: 'frontend',
|
|
2064
|
-
} );
|
|
2065
|
-
if ( ! result.isValid || ! result.data ) {
|
|
2066
|
-
lastBootstrapError =
|
|
2067
|
-
result.errors[ 0 ]?.expected ??
|
|
2068
|
-
'Unable to initialize write access';
|
|
2069
|
-
if ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {
|
|
2070
|
-
await waitForBootstrapRetry(
|
|
2071
|
-
BOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750
|
|
2072
|
-
);
|
|
2073
|
-
continue;
|
|
2074
|
-
}
|
|
2075
|
-
break;
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
clientState.writeExpiry =
|
|
2079
|
-
includePublicWriteCredentials &&
|
|
2080
|
-
'publicWriteExpiresAt' in result.data &&
|
|
2081
|
-
typeof result.data.publicWriteExpiresAt === 'number' &&
|
|
2082
|
-
result.data.publicWriteExpiresAt > 0
|
|
2083
|
-
? result.data.publicWriteExpiresAt
|
|
2084
|
-
: 0;
|
|
2085
|
-
clientState.writeToken =
|
|
2086
|
-
includePublicWriteCredentials &&
|
|
2087
|
-
'publicWriteToken' in result.data &&
|
|
2088
|
-
typeof result.data.publicWriteToken === 'string' &&
|
|
2089
|
-
result.data.publicWriteToken.length > 0
|
|
2090
|
-
? result.data.publicWriteToken
|
|
2091
|
-
: '';
|
|
2092
|
-
clientState.writeNonce =
|
|
2093
|
-
includeRestNonce &&
|
|
2094
|
-
'restNonce' in result.data &&
|
|
2095
|
-
typeof result.data.restNonce === 'string' &&
|
|
2096
|
-
result.data.restNonce.length > 0
|
|
2097
|
-
? result.data.restNonce
|
|
2098
|
-
: '';
|
|
2099
|
-
context.bootstrapReady = true;
|
|
2100
|
-
context.canWrite =
|
|
2101
|
-
result.data.canWrite === true &&
|
|
2102
|
-
(
|
|
2103
|
-
context.persistencePolicy === 'authenticated'
|
|
2104
|
-
? clientState.writeNonce.length > 0
|
|
2105
|
-
: clientState.writeToken.length > 0 &&
|
|
2106
|
-
! hasExpiredPublicWriteToken( clientState.writeExpiry )
|
|
2107
|
-
);
|
|
2108
|
-
clearBootstrapError( context, clientState );
|
|
2109
|
-
bootstrapSucceeded = true;
|
|
2110
|
-
break;
|
|
2111
|
-
} catch ( error ) {
|
|
2112
|
-
lastBootstrapError =
|
|
2113
|
-
error instanceof Error ? error.message : 'Unknown bootstrap error';
|
|
2114
|
-
if ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {
|
|
2115
|
-
await waitForBootstrapRetry(
|
|
2116
|
-
BOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750
|
|
2117
|
-
);
|
|
2118
|
-
continue;
|
|
2119
|
-
}
|
|
2120
|
-
break;
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
if ( ! bootstrapSucceeded ) {
|
|
2125
|
-
context.bootstrapReady = false;
|
|
2126
|
-
context.canWrite = false;
|
|
2127
|
-
clientState.writeExpiry = 0;
|
|
2128
|
-
clientState.writeNonce = '';
|
|
2129
|
-
clientState.writeToken = '';
|
|
2130
|
-
setBootstrapError( context, clientState, lastBootstrapError );
|
|
2131
|
-
}
|
|
2132
|
-
context.isBootstrapping = false;
|
|
2133
|
-
},
|
|
2134
|
-
async increment() {
|
|
2135
|
-
const context = getContext< {{pascalCase}}Context >();
|
|
2136
|
-
const clientState = getClientState( context );
|
|
2137
|
-
if ( context.postId <= 0 || ! context.resourceKey ) {
|
|
2138
|
-
return;
|
|
2139
|
-
}
|
|
2140
|
-
if ( ! context.bootstrapReady ) {
|
|
2141
|
-
await actions.loadBootstrap();
|
|
2142
|
-
}
|
|
2143
|
-
if ( ! context.bootstrapReady ) {
|
|
2144
|
-
context.error = 'Write access is still initializing.';
|
|
2145
|
-
return;
|
|
2146
|
-
}
|
|
2147
|
-
if (
|
|
2148
|
-
context.persistencePolicy === 'public' &&
|
|
2149
|
-
hasExpiredPublicWriteToken( clientState.writeExpiry )
|
|
2150
|
-
) {
|
|
2151
|
-
await actions.loadBootstrap();
|
|
2152
|
-
}
|
|
2153
|
-
if (
|
|
2154
|
-
context.persistencePolicy === 'public' &&
|
|
2155
|
-
hasExpiredPublicWriteToken( clientState.writeExpiry )
|
|
2156
|
-
) {
|
|
2157
|
-
context.canWrite = false;
|
|
2158
|
-
context.error = getWriteBlockedMessage( context );
|
|
2159
|
-
return;
|
|
2160
|
-
}
|
|
2161
|
-
if ( ! context.canWrite ) {
|
|
2162
|
-
context.error = getWriteBlockedMessage( context );
|
|
2163
|
-
return;
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
context.isSaving = true;
|
|
2167
|
-
context.error = '';
|
|
2168
|
-
|
|
2169
|
-
try {
|
|
2170
|
-
const result = await writeState( {
|
|
2171
|
-
delta: 1,
|
|
2172
|
-
postId: context.postId,
|
|
2173
|
-
publicWriteRequestId:
|
|
2174
|
-
context.persistencePolicy === 'public'
|
|
2175
|
-
? generatePublicWriteRequestId()
|
|
2176
|
-
: undefined,
|
|
2177
|
-
publicWriteToken:
|
|
2178
|
-
context.persistencePolicy === 'public' &&
|
|
2179
|
-
clientState.writeToken.length > 0
|
|
2180
|
-
? clientState.writeToken
|
|
2181
|
-
: undefined,
|
|
2182
|
-
resourceKey: context.resourceKey,
|
|
2183
|
-
}, {
|
|
2184
|
-
restNonce:
|
|
2185
|
-
clientState.writeNonce.length > 0
|
|
2186
|
-
? clientState.writeNonce
|
|
2187
|
-
: undefined,
|
|
2188
|
-
transportTarget: 'frontend',
|
|
2189
|
-
} );
|
|
2190
|
-
if ( ! result.isValid || ! result.data ) {
|
|
2191
|
-
context.error = result.errors[ 0 ]?.expected ?? 'Unable to update counter';
|
|
2192
|
-
return;
|
|
2193
|
-
}
|
|
2194
|
-
context.count = result.data.count;
|
|
2195
|
-
context.storage = result.data.storage;
|
|
2196
|
-
} catch ( error ) {
|
|
2197
|
-
context.error =
|
|
2198
|
-
error instanceof Error ? error.message : 'Unknown update error';
|
|
2199
|
-
} finally {
|
|
2200
|
-
context.isSaving = false;
|
|
2201
|
-
}
|
|
2202
|
-
},
|
|
2203
|
-
},
|
|
2204
|
-
|
|
2205
|
-
callbacks: {
|
|
2206
|
-
init() {
|
|
2207
|
-
const context = getContext< {{pascalCase}}Context >();
|
|
2208
|
-
context.client = {
|
|
2209
|
-
bootstrapError: '',
|
|
2210
|
-
writeExpiry: 0,
|
|
2211
|
-
writeNonce: '',
|
|
2212
|
-
writeToken: '',
|
|
2213
|
-
};
|
|
2214
|
-
context.bootstrapReady = false;
|
|
2215
|
-
context.canWrite = false;
|
|
2216
|
-
context.count = 0;
|
|
2217
|
-
context.error = '';
|
|
2218
|
-
context.isBootstrapping = false;
|
|
2219
|
-
context.isLoading = false;
|
|
2220
|
-
context.isSaving = false;
|
|
2221
|
-
},
|
|
2222
|
-
mounted() {
|
|
2223
|
-
state.isHydrated = true;
|
|
2224
|
-
if ( typeof document !== 'undefined' ) {
|
|
2225
|
-
document.documentElement.dataset[ '{{slugCamelCase}}Hydrated' ] = 'true';
|
|
2226
|
-
}
|
|
2227
|
-
void Promise.allSettled( [
|
|
2228
|
-
actions.loadState(),
|
|
2229
|
-
actions.loadBootstrap(),
|
|
2230
|
-
] );
|
|
2231
|
-
},
|
|
2232
|
-
},
|
|
2233
|
-
} );
|
|
2234
|
-
`;
|
|
5
|
+
export { BLOCK_METADATA_WRAPPER_TEMPLATE, MANIFEST_DEFAULTS_DOCUMENT_WRAPPER_TEMPLATE, MANIFEST_DOCUMENT_WRAPPER_TEMPLATE, SHARED_HOOKS_TEMPLATE, } from "./built-in-block-code-templates/shared.js";
|
|
6
|
+
export { BASIC_EDIT_TEMPLATE, BASIC_INDEX_TEMPLATE, BASIC_SAVE_TEMPLATE, BASIC_VALIDATORS_TEMPLATE, } from "./built-in-block-code-templates/basic.js";
|
|
7
|
+
export { INTERACTIVITY_EDIT_TEMPLATE, INTERACTIVITY_INDEX_TEMPLATE, INTERACTIVITY_SAVE_TEMPLATE, INTERACTIVITY_SCRIPT_TEMPLATE, INTERACTIVITY_VALIDATORS_TEMPLATE, } from "./built-in-block-code-templates/interactivity.js";
|
|
8
|
+
export { PERSISTENCE_EDIT_TEMPLATE, PERSISTENCE_INDEX_TEMPLATE, PERSISTENCE_INTERACTIVITY_TEMPLATE, PERSISTENCE_SAVE_TEMPLATE, PERSISTENCE_VALIDATORS_TEMPLATE, } from "./built-in-block-code-templates/persistence.js";
|
|
9
|
+
export { COMPOUND_CHILD_EDIT_TEMPLATE, COMPOUND_CHILD_INDEX_TEMPLATE, COMPOUND_CHILDREN_TEMPLATE, COMPOUND_CHILD_SAVE_TEMPLATE, COMPOUND_CHILD_VALIDATORS_TEMPLATE, COMPOUND_LOCAL_HOOKS_TEMPLATE, COMPOUND_PARENT_EDIT_TEMPLATE, COMPOUND_PARENT_INDEX_TEMPLATE, COMPOUND_PARENT_SAVE_TEMPLATE, COMPOUND_PARENT_VALIDATORS_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_INTERACTIVITY_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_VALIDATORS_TEMPLATE, } from "./built-in-block-code-templates/compound.js";
|