@wp-typia/project-tools 0.16.6 → 0.16.7
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 -7
- package/dist/runtime/built-in-block-code-artifacts.js +5 -5
- package/dist/runtime/cli-add-block.d.ts +36 -0
- package/dist/runtime/cli-add-block.js +518 -0
- package/dist/runtime/cli-add-shared.d.ts +93 -0
- package/dist/runtime/cli-add-shared.js +201 -0
- package/dist/runtime/cli-add-workspace.d.ts +81 -0
- package/dist/runtime/cli-add-workspace.js +582 -0
- package/dist/runtime/cli-add.d.ts +11 -131
- package/dist/runtime/cli-add.js +10 -1250
- package/dist/runtime/cli-prompt.d.ts +25 -0
- package/dist/runtime/cli-prompt.js +32 -20
- package/dist/runtime/cli-scaffold.js +1 -2
- package/dist/runtime/migration-types.d.ts +9 -53
- package/dist/runtime/scaffold.js +1 -2
- package/dist/runtime/template-source.js +1 -2
- package/package.json +6 -8
- package/templates/_shared/base/package.json.mustache +1 -0
- package/templates/_shared/compound/core/package.json.mustache +1 -1
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +5 -5
- package/templates/_shared/compound/persistence/package.json.mustache +1 -1
- package/templates/_shared/persistence/core/package.json.mustache +1 -0
- package/templates/interactivity/package.json.mustache +2 -1
package/README.md
CHANGED
|
@@ -23,21 +23,23 @@ Implementation note:
|
|
|
23
23
|
- `@wp-typia/project-tools/schema-core` remains the preferred project-tooling
|
|
24
24
|
import path.
|
|
25
25
|
- The shared implementation now lives in `@wp-typia/block-runtime/schema-core`.
|
|
26
|
+
- Shared manifest/migration contract types now live in
|
|
27
|
+
`@wp-typia/block-runtime/migration-types`.
|
|
26
28
|
|
|
27
29
|
Example:
|
|
28
30
|
|
|
29
31
|
```ts
|
|
30
32
|
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} from
|
|
33
|
+
BlockGeneratorService,
|
|
34
|
+
getTemplateById,
|
|
35
|
+
parseMigrationArgs,
|
|
36
|
+
projectJsonSchemaDocument,
|
|
37
|
+
resolvePackageManagerId,
|
|
38
|
+
} from '@wp-typia/project-tools';
|
|
37
39
|
```
|
|
38
40
|
|
|
39
41
|
```ts
|
|
40
|
-
import { normalizeEndpointAuthDefinition } from
|
|
42
|
+
import { normalizeEndpointAuthDefinition } from '@wp-typia/project-tools/schema-core';
|
|
41
43
|
```
|
|
42
44
|
|
|
43
45
|
`BlockGeneratorService` is the additive typed orchestration boundary for built-in
|
|
@@ -5,27 +5,27 @@ const BASIC_EDIT_TEMPLATE = "/**\n * Editor component for {{title}} Block\n */\n
|
|
|
5
5
|
const BASIC_SAVE_TEMPLATE = "/**\n * Save/Frontend component for {{title}} Block\n */\n\nimport { RichText, useBlockProps } from '@wordpress/block-editor';\nimport { {{pascalCase}}Attributes } from './types';\n\ninterface SaveProps {\n attributes: {{pascalCase}}Attributes;\n}\n\nexport default function Save({ attributes }: SaveProps) {\n const isVisible = attributes.isVisible !== false;\n const blockProps = useBlockProps.save({\n className: `{{cssClassName}}${isVisible ? '' : ' is-hidden'}`,\n hidden: isVisible ? undefined : true,\n 'aria-hidden': isVisible ? undefined : 'true',\n });\n\n return (\n <div {...blockProps}>\n <div\n className=\"{{cssClassName}}__content\"\n data-align={attributes.alignment || 'left'}\n >\n <RichText.Content tagName=\"p\" value={attributes.content} />\n </div>\n </div>\n );\n}\n";
|
|
6
6
|
const BASIC_INDEX_TEMPLATE = "/**\n * WordPress {{title}} Block\n *\n * Typia-powered type-safe block\n */\n\nimport { registerBlockType } from '@wordpress/blocks';\nimport type { BlockConfiguration } from '@wordpress/blocks';\nimport type { BlockSupports } from '@wp-typia/block-types/blocks/supports';\nimport { __ } from '@wordpress/i18n';\nimport {\n buildScaffoldBlockRegistration,\n type ScaffoldBlockMetadata,\n} from '@wp-typia/block-runtime/blocks';\n\n// Import components\nimport Edit from './edit';\nimport Save from './save';\nimport metadata from './block.json';\nimport './editor.scss';\nimport './style.scss';\n\n// Import types\nimport { {{pascalCase}}Attributes } from './types';\nimport { validators } from './validators';\n\nconst scaffoldSupports = {\n html: false,\n multiple: true,\n align: ['wide', 'full'],\n} satisfies BlockSupports;\n\n// Register the block\nconst registration = buildScaffoldBlockRegistration<\n BlockConfiguration<{{pascalCase}}Attributes>\n>(metadata as ScaffoldBlockMetadata, {\n supports: scaffoldSupports,\n example: {\n attributes: validators.random(),\n },\n edit: Edit,\n save: Save,\n});\n\nregisterBlockType<{{pascalCase}}Attributes>(registration.name, registration.settings);\n";
|
|
7
7
|
const BASIC_VALIDATORS_TEMPLATE = "import typia from 'typia';\nimport currentManifest from \"./typia.manifest.json\";\nimport { {{pascalCase}}Attributes, {{pascalCase}}ValidationResult } from \"./types\";\nimport { generateBlockId } from \"@wp-typia/block-runtime/identifiers\";\nimport { createTemplateValidatorToolkit } from \"./validator-toolkit\";\n\nconst scaffoldValidators = createTemplateValidatorToolkit<{{pascalCase}}Attributes>({\n assert: typia.createAssert<{{pascalCase}}Attributes>(),\n clone: typia.misc.createClone<{{pascalCase}}Attributes>() as (\n value: {{pascalCase}}Attributes,\n ) => {{pascalCase}}Attributes,\n is: typia.createIs<{{pascalCase}}Attributes>(),\n manifest: currentManifest,\n prune: typia.misc.createPrune<{{pascalCase}}Attributes>(),\n random: typia.createRandom<{{pascalCase}}Attributes>() as (\n ...args: unknown[]\n ) => {{pascalCase}}Attributes,\n finalize: (normalized) => ({\n ...normalized,\n id: normalized.id && normalized.id.length > 0 ? normalized.id : generateBlockId(),\n }),\n validate: typia.createValidate<{{pascalCase}}Attributes>(),\n});\n\nexport const validate{{pascalCase}}Attributes =\n scaffoldValidators.validateAttributes as (\n attributes: unknown,\n ) => {{pascalCase}}ValidationResult;\n\nexport const validators = scaffoldValidators.validators;\n\nexport const sanitize{{pascalCase}}Attributes =\n scaffoldValidators.sanitizeAttributes as (\n attributes: Partial<{{pascalCase}}Attributes>,\n ) => {{pascalCase}}Attributes;\n\nexport const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;\n";
|
|
8
|
-
const INTERACTIVITY_EDIT_TEMPLATE = "import { __ } from '@wordpress/i18n';\nimport { useBlockProps, InspectorControls, RichText, BlockControls, AlignmentToolbar } from '@wordpress/block-editor';\nimport { PanelBody, RangeControl, Button, Notice } from '@wordpress/components';\nimport { useState } from '@wordpress/element';\nimport currentManifest from './typia.manifest.json';\nimport {\n InspectorFromManifest,\n type ManifestDocument,\n useEditorFields,\n useTypedAttributeUpdater,\n} from '@wp-typia/block-runtime/inspector';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n sanitize{{pascalCase}}Attributes,\n validate{{pascalCase}}Attributes,\n} from './validators';\nimport { useTypiaValidation } from './hooks';\n\nconst actionButtonRowStyle = { display: 'flex', gap: '8px', marginTop: '16px' };\nconst validationListStyle = { margin: 0, paddingLeft: '1em' };\n\nexport default function Edit({ attributes, setAttributes, isSelected }:
|
|
8
|
+
const INTERACTIVITY_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wordpress/blocks';\nimport { __ } from '@wordpress/i18n';\nimport { useBlockProps, InspectorControls, RichText, BlockControls, AlignmentToolbar } from '@wordpress/block-editor';\nimport { PanelBody, RangeControl, Button, Notice } from '@wordpress/components';\nimport { useState } from '@wordpress/element';\nimport currentManifest from './typia.manifest.json';\nimport {\n InspectorFromManifest,\n type ManifestDocument,\n useEditorFields,\n useTypedAttributeUpdater,\n} from '@wp-typia/block-runtime/inspector';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n sanitize{{pascalCase}}Attributes,\n validate{{pascalCase}}Attributes,\n} from './validators';\nimport { useTypiaValidation } from './hooks';\n\ntype EditProps = BlockEditProps<{{pascalCase}}Attributes>;\n\nconst actionButtonRowStyle = { display: 'flex', gap: '8px', marginTop: '16px' };\nconst validationListStyle = { margin: 0, paddingLeft: '1em' };\n\nexport default function Edit({ attributes, setAttributes, isSelected }: EditProps) {\n const [isPreviewing, setIsPreviewing] = useState(false);\n const editorFields = useEditorFields(currentManifest as ManifestDocument, {\n manual: ['content', 'clickCount', 'maxClicks'],\n labels: {\n alignment: __('Alignment', '{{textDomain}}'),\n animation: __('Animation', '{{textDomain}}'),\n interactiveMode: __('Interactive Mode', '{{textDomain}}'),\n isVisible: __('Visible', '{{textDomain}}'),\n showCounter: __('Show Counter', '{{textDomain}}'),\n },\n });\n const { errorMessages, isValid } = useTypiaValidation(\n attributes,\n validate{{pascalCase}}Attributes,\n );\n const validateEditorUpdate = (nextAttributes: {{pascalCase}}Attributes) => {\n try {\n return {\n data: sanitize{{pascalCase}}Attributes(nextAttributes),\n errors: [],\n isValid: true as const,\n };\n } catch {\n return validate{{pascalCase}}Attributes(nextAttributes);\n }\n };\n const { updateField } = useTypedAttributeUpdater(\n attributes,\n setAttributes,\n validateEditorUpdate\n );\n const alignmentValue = editorFields.getStringValue(\n attributes,\n 'alignment',\n 'left'\n ) as NonNullable<{{pascalCase}}Attributes['alignment']>;\n const clickCount = attributes.clickCount ?? 0;\n const isVisible = editorFields.getBooleanValue(\n attributes,\n 'isVisible',\n true\n );\n const isAnimating = attributes.isAnimating ?? false;\n const maxClicks = attributes.maxClicks ?? 0;\n const showCounter = editorFields.getBooleanValue(\n attributes,\n 'showCounter',\n true\n );\n const interactiveMode = editorFields.getStringValue(\n attributes,\n 'interactiveMode',\n 'click'\n ) as NonNullable<{{pascalCase}}Attributes['interactiveMode']>;\n const animation = editorFields.getStringValue(\n attributes,\n 'animation',\n 'none'\n ) as NonNullable<{{pascalCase}}Attributes['animation']>;\n\n const blockProps = useBlockProps({\n className: `{{cssClassName}} {{cssClassName}}--${interactiveMode}`,\n 'data-wp-interactive': '{{slugKebabCase}}',\n 'data-wp-context': JSON.stringify({\n clicks: clickCount,\n isAnimating,\n isVisible,\n animation,\n maxClicks,\n })\n });\n const previewContentStyle = { textAlign: alignmentValue };\n const progressBarStyle = { width: `${(clickCount / maxClicks) * 100}%` };\n\n const resetCounter = () => {\n updateField('clickCount', 0);\n updateField('isAnimating', false);\n };\n\n const testAnimation = () => {\n updateField('isAnimating', true);\n setTimeout(() => {\n updateField('isAnimating', false);\n }, 1000);\n };\n\n return (\n <>\n <BlockControls>\n <AlignmentToolbar\n value={alignmentValue}\n onChange={(value) => updateField('alignment', (value || alignmentValue) as NonNullable<{{pascalCase}}Attributes['alignment']>)}\n />\n </BlockControls>\n\n <InspectorControls>\n <InspectorFromManifest\n attributes={attributes}\n fieldLookup={editorFields}\n onChange={updateField}\n paths={['alignment', 'interactiveMode', 'animation', 'showCounter', 'isVisible']}\n title={__('Interactive Settings', '{{textDomain}}')}\n />\n\n <PanelBody title={__('Counter Settings', '{{textDomain}}')}>\n <RangeControl\n label={__('Max Clicks', '{{textDomain}}')}\n value={maxClicks}\n onChange={(value) => updateField('maxClicks', value ?? maxClicks)}\n min={0}\n max={100}\n help={__('Set to 0 for unlimited clicks', '{{textDomain}}')}\n />\n\n <div style={actionButtonRowStyle}>\n <Button\n variant=\"secondary\"\n onClick={resetCounter}\n isSmall\n >\n {__('Reset Counter', '{{textDomain}}')}\n </Button>\n <Button\n variant=\"secondary\"\n onClick={testAnimation}\n isSmall\n >\n {__('Test Animation', '{{textDomain}}')}\n </Button>\n </div>\n </PanelBody>\n\n {!isValid && (\n <PanelBody title={__('Validation Errors', '{{textDomain}}')} initialOpen>\n {errorMessages.map((error, index) => (\n <Notice key={index} status=\"error\" isDismissible={false}>\n {error}\n </Notice>\n ))}\n </PanelBody>\n )}\n\n {isSelected && (\n <PanelBody title={__('Preview', '{{textDomain}}')}>\n <Button\n variant={isPreviewing ? 'primary' : 'secondary'}\n onClick={() => setIsPreviewing(!isPreviewing)}\n aria-pressed={isPreviewing}\n isSmall\n >\n {isPreviewing\n ? __('Disable Preview Mode', '{{textDomain}}')\n : __('Enable Preview Mode', '{{textDomain}}')}\n </Button>\n\n {clickCount > 0 && (\n <Notice status=\"info\" isDismissible={false}>\n {__('Current clicks:', '{{textDomain}}')} {clickCount}\n {maxClicks > 0 && ` / ${maxClicks}`}\n </Notice>\n )}\n </PanelBody>\n )}\n </InspectorControls>\n\n <div {...blockProps}>\n <div\n className={`{{cssClassName}}__content ${isAnimating ? 'is-animating' : ''}`}\n style={previewContentStyle}\n data-wp-on--click={isPreviewing ? 'actions.handleClick' : undefined}\n data-wp-on--mouseenter={isPreviewing && interactiveMode === 'hover' ? 'actions.handleMouseEnter' : undefined}\n data-wp-on--mouseleave={isPreviewing && interactiveMode === 'hover' ? 'actions.handleMouseLeave' : undefined}\n >\n <RichText\n tagName=\"p\"\n value={attributes.content}\n onChange={(value) => updateField('content', value)}\n placeholder={__( {{titleJson}} + ' – click me to interact!', '{{textDomain}}')}\n />\n\n {!isValid && (\n <Notice status=\"error\" isDismissible={false}>\n <p>\n <strong>{__('Validation Errors', '{{textDomain}}')}</strong>\n </p>\n <ul style={validationListStyle}>\n {errorMessages.map((error, index) => (\n <li key={index}>{error}</li>\n ))}\n </ul>\n </Notice>\n )}\n\n {showCounter && (\n <div className=\"{{cssClassName}}__counter\">\n <span className=\"{{cssClassName}}__counter-label\">\n {__('Clicks:', '{{textDomain}}')}\n </span>\n <span\n className=\"{{cssClassName}}__counter-value\"\n data-wp-text=\"state.clicks\"\n >\n {clickCount}\n </span>\n </div>\n )}\n\n {maxClicks > 0 && (\n <div className=\"{{cssClassName}}__progress\">\n <div\n className=\"{{cssClassName}}__progress-bar\"\n style={progressBarStyle}\n data-wp-style--width=\"state.progress + '%'\"\n />\n </div>\n )}\n\n {animation !== 'none' && (\n <div\n className={`{{cssClassName}}__animation ${isAnimating ? 'is-active' : ''}`}\n data-wp-class--is-active=\"state.isAnimating\"\n >\n {animation}\n </div>\n )}\n </div>\n </div>\n </>\n );\n}\n";
|
|
9
9
|
const INTERACTIVITY_SAVE_TEMPLATE = "import { useBlockProps, RichText } from '@wordpress/block-editor';\nimport { __ } from '@wordpress/i18n';\nimport type { {{pascalCase}}Attributes } from './types';\n\nexport default function Save({ attributes }: { attributes: {{pascalCase}}Attributes }) {\n const clickCount = attributes.clickCount ?? 0;\n const interactiveMode = attributes.interactiveMode ?? 'click';\n const animation = attributes.animation ?? 'none';\n const isAnimating = attributes.isAnimating ?? false;\n const isVisible = attributes.isVisible ?? true;\n const maxClicks = attributes.maxClicks ?? 0;\n const showCounter = attributes.showCounter ?? true;\n const contentStyle = { textAlign: attributes.alignment };\n const blockProps = useBlockProps.save({\n className: `{{cssClassName}} {{cssClassName}}--${interactiveMode}`,\n 'data-wp-interactive': '{{slugKebabCase}}',\n 'data-wp-context': JSON.stringify({\n clicks: clickCount,\n isAnimating,\n isVisible,\n animation,\n maxClicks,\n })\n });\n\n return (\n <div {...blockProps}>\n <div\n className={`{{cssClassName}}__content ${isAnimating ? 'is-animating' : ''}`}\n style={contentStyle}\n data-wp-on--click=\"actions.handleClick\"\n data-wp-on--mouseenter={interactiveMode === 'hover' ? 'actions.handleMouseEnter' : undefined}\n data-wp-on--mouseleave={interactiveMode === 'hover' ? 'actions.handleMouseLeave' : undefined}\n data-wp-bind--hidden=\"!state.isVisible\"\n >\n <RichText.Content\n tagName=\"p\"\n value={attributes.content}\n className=\"{{cssClassName}}__text\"\n />\n\n {showCounter && (\n <div\n className=\"{{cssClassName}}__counter\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >\n <span className=\"{{cssClassName}}__counter-label\">\n { __( 'Clicks:', '{{textDomain}}' ) }\n </span>\n <span\n className=\"{{cssClassName}}__counter-value\"\n data-wp-text=\"state.clicks\"\n >\n {clickCount}\n </span>\n </div>\n )}\n\n {maxClicks > 0 && (\n <div className=\"{{cssClassName}}__progress\">\n <div\n className=\"{{cssClassName}}__progress-bar\"\n role=\"progressbar\"\n aria-label={ __( 'Click progress', '{{textDomain}}' ) }\n aria-valuemin={0}\n aria-valuemax={maxClicks}\n aria-valuenow={Math.min(clickCount, maxClicks)}\n data-wp-bind--aria-valuenow=\"state.clampedClicks\"\n data-wp-style--width=\"state.progress + '%'\"\n />\n </div>\n )}\n\n <div\n className={`{{cssClassName}}__animation ${animation}`}\n aria-hidden=\"true\"\n data-wp-class--is-active=\"state.isAnimating\"\n />\n\n {maxClicks > 0 && (\n <div\n className=\"{{cssClassName}}__completion\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n data-wp-bind--hidden=\"!state.isComplete\"\n >\n { __( '🎉 Complete!', '{{textDomain}}' ) }\n </div>\n )}\n\n <button\n className=\"{{cssClassName}}__reset\"\n data-wp-on--click=\"actions.reset\"\n aria-label={ __( 'Reset counter', '{{textDomain}}' ) }\n >\n <span aria-hidden=\"true\">↻</span>\n <span className=\"screen-reader-text\">\n { __( 'Reset counter', '{{textDomain}}' ) }\n </span>\n </button>\n </div>\n </div>\n );\n}\n";
|
|
10
10
|
const INTERACTIVITY_INDEX_TEMPLATE = "import { registerBlockType } from '@wordpress/blocks';\nimport type { BlockConfiguration } from '@wordpress/blocks';\nimport type { BlockSupports } from '@wp-typia/block-types/blocks/supports';\nimport {\n buildScaffoldBlockRegistration,\n type ScaffoldBlockMetadata,\n} from '@wp-typia/block-runtime/blocks';\n\nimport Edit from './edit';\nimport Save from './save';\nimport metadata from './block.json';\nimport './editor.scss';\nimport './style.scss';\n\nimport type { {{pascalCase}}Attributes } from './types';\n\nconst scaffoldSupports = {\n html: false,\n align: true,\n anchor: true,\n className: true,\n interactivity: true,\n} satisfies BlockSupports;\n\nconst registration = buildScaffoldBlockRegistration<\n BlockConfiguration<{{pascalCase}}Attributes>\n>(metadata as ScaffoldBlockMetadata, {\n supports: scaffoldSupports,\n edit: Edit,\n save: Save,\n});\n\nregisterBlockType<{{pascalCase}}Attributes>(registration.name, registration.settings);\n";
|
|
11
11
|
const INTERACTIVITY_SCRIPT_TEMPLATE = "/**\n * WordPress Interactivity API implementation for {{title}} block\n */\nimport { store, getContext, getElement, withSyncEvent } from '@wordpress/interactivity';\nimport type { {{pascalCase}}Context } from './types';\n\nfunction getBlockContext() {\n return getContext<{{pascalCase}}Context>();\n}\n\n// Store configuration\nstore('{{slugKebabCase}}', {\n // State - reactive data that updates the UI\n state: {\n get clicks() {\n return getBlockContext().clicks;\n },\n get isAnimating() {\n return getBlockContext().isAnimating;\n },\n get isVisible() {\n return getBlockContext().isVisible;\n },\n get progress() {\n const context = getBlockContext();\n const clampedClicks =\n context.maxClicks > 0\n ? Math.min(context.clicks, context.maxClicks)\n : context.clicks;\n return context.maxClicks > 0 ? (clampedClicks / context.maxClicks) * 100 : 0;\n },\n get clampedClicks() {\n const context = getBlockContext();\n return context.maxClicks > 0\n ? Math.min(context.clicks, context.maxClicks)\n : context.clicks;\n },\n get isComplete() {\n const context = getBlockContext();\n return context.clicks >= context.maxClicks && context.maxClicks > 0;\n }\n },\n\n // Actions - user interactions\n actions: {\n // Handle block click\n handleClick: () => {\n const context = getBlockContext();\n const { ref } = getElement();\n\n if (!ref) {\n return;\n }\n\n if (context.maxClicks > 0 && context.clicks >= context.maxClicks) {\n return;\n }\n\n const previousClicks = context.clicks;\n\n // Increment click counter\n context.clicks += 1;\n\n // Trigger animation\n if (context.animation !== 'none') {\n context.isAnimating = true;\n setTimeout(() => {\n context.isAnimating = false;\n }, 1000);\n }\n\n // Emit custom event\n ref.dispatchEvent(new CustomEvent('{{slugKebabCase}}:click', {\n detail: { clicks: context.clicks }\n }));\n\n // Check if max clicks reached\n if (context.maxClicks > 0 && previousClicks < context.maxClicks && context.clicks === context.maxClicks) {\n ref.dispatchEvent(new CustomEvent('{{slugKebabCase}}:complete', {\n detail: { totalClicks: context.clicks }\n }));\n }\n },\n\n // Handle hover events\n handleMouseEnter: () => {\n const context = getBlockContext();\n if (context.animation === 'none') return;\n context.isAnimating = true;\n },\n\n handleMouseLeave: () => {\n const context = getBlockContext();\n if (context.animation === 'none') return;\n context.isAnimating = false;\n },\n\n // Reset counter\n reset: withSyncEvent((event: Event) => {\n event.stopPropagation();\n const context = getBlockContext();\n context.clicks = 0;\n context.isAnimating = false;\n })\n }\n});\n";
|
|
12
12
|
const INTERACTIVITY_VALIDATORS_TEMPLATE = "import typia from 'typia';\nimport currentManifest from \"./typia.manifest.json\";\nimport { {{pascalCase}}Attributes, {{pascalCase}}ValidationResult } from \"./types\";\nimport { createTemplateValidatorToolkit } from \"./validator-toolkit\";\n\nconst scaffoldValidators = createTemplateValidatorToolkit<{{pascalCase}}Attributes>({\n assert: typia.createAssert<{{pascalCase}}Attributes>(),\n clone: typia.misc.createClone<{{pascalCase}}Attributes>() as (\n value: {{pascalCase}}Attributes,\n ) => {{pascalCase}}Attributes,\n is: typia.createIs<{{pascalCase}}Attributes>(),\n manifest: currentManifest,\n prune: typia.misc.createPrune<{{pascalCase}}Attributes>(),\n random: typia.createRandom<{{pascalCase}}Attributes>() as (\n ...args: unknown[]\n ) => {{pascalCase}}Attributes,\n validate: typia.createValidate<{{pascalCase}}Attributes>(),\n});\n\nexport const validate{{pascalCase}}Attributes =\n scaffoldValidators.validateAttributes as (\n attributes: unknown,\n ) => {{pascalCase}}ValidationResult;\n\nexport const validators = scaffoldValidators.validators;\n\nexport const sanitize{{pascalCase}}Attributes =\n scaffoldValidators.sanitizeAttributes as (\n attributes: Partial<{{pascalCase}}Attributes>,\n ) => {{pascalCase}}Attributes;\n\n/**\n * Runtime type guard for checking if an object is {{pascalCase}}Attributes.\n */\nexport const is{{pascalCase}}Attributes = (obj: unknown): obj is {{pascalCase}}Attributes => {\n return validators.is(obj);\n};\n\nexport const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;\n";
|
|
13
|
-
const PERSISTENCE_EDIT_TEMPLATE = "import { __ } from '@wordpress/i18n';\nimport {\n\tAlignmentToolbar,\n\tBlockControls,\n\tInspectorControls,\n\tRichText,\n\tuseBlockProps,\n} from '@wordpress/block-editor';\nimport {\n\tNotice,\n\tPanelBody,\n\tTextControl,\n} from '@wordpress/components';\nimport currentManifest from './typia.manifest.json';\nimport {\n\tInspectorFromManifest,\n\ttype ManifestDocument,\n\tuseEditorFields,\n\tuseTypedAttributeUpdater,\n} from '@wp-typia/block-runtime/inspector';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n\tsanitize{{pascalCase}}Attributes,\n\tvalidate{{pascalCase}}Attributes,\n} from './validators';\nimport { useTypiaValidation } from './hooks';\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}:
|
|
13
|
+
const PERSISTENCE_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wordpress/blocks';\nimport { __ } from '@wordpress/i18n';\nimport {\n\tAlignmentToolbar,\n\tBlockControls,\n\tInspectorControls,\n\tRichText,\n\tuseBlockProps,\n} from '@wordpress/block-editor';\nimport {\n\tNotice,\n\tPanelBody,\n\tTextControl,\n} from '@wordpress/components';\nimport currentManifest from './typia.manifest.json';\nimport {\n\tInspectorFromManifest,\n\ttype ManifestDocument,\n\tuseEditorFields,\n\tuseTypedAttributeUpdater,\n} from '@wp-typia/block-runtime/inspector';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n\tsanitize{{pascalCase}}Attributes,\n\tvalidate{{pascalCase}}Attributes,\n} from './validators';\nimport { useTypiaValidation } from './hooks';\n\ntype EditProps = BlockEditProps< {{pascalCase}}Attributes >;\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}: EditProps ) {\n\tconst editorFields = useEditorFields(\n\t\tcurrentManifest as ManifestDocument,\n\t\t{\n\t\t\tmanual: [ 'content', 'resourceKey' ],\n\t\t\tlabels: {\n\t\t\t\tbuttonLabel: __( 'Button Label', '{{textDomain}}' ),\n\t\t\t\tresourceKey: __( 'Resource Key', '{{textDomain}}' ),\n\t\t\t\tshowCount: __( 'Show Count', '{{textDomain}}' ),\n\t\t\t},\n\t\t}\n\t);\n\tconst { errorMessages, isValid } = useTypiaValidation(\n\t\tattributes,\n\t\tvalidate{{pascalCase}}Attributes\n\t);\n\tconst validateEditorUpdate = (\n\t\tnextAttributes: {{pascalCase}}Attributes\n\t) => {\n\t\ttry {\n\t\t\treturn {\n\t\t\t\tdata: sanitize{{pascalCase}}Attributes( nextAttributes ),\n\t\t\t\terrors: [],\n\t\t\t\tisValid: true as const,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn validate{{pascalCase}}Attributes( nextAttributes );\n\t\t}\n\t};\n\tconst { updateField } = useTypedAttributeUpdater(\n\t\tattributes,\n\t\tsetAttributes,\n\t\tvalidateEditorUpdate\n\t);\n\tconst alignmentValue = editorFields.getStringValue(\n\t\tattributes,\n\t\t'alignment',\n\t\t'left'\n\t);\n\tconst persistencePolicy = '{{persistencePolicy}}';\n\tconst persistencePolicyDescription = __(\n\t\t{{persistencePolicyDescriptionJson}},\n\t\t'{{textDomain}}'\n\t);\n\n\treturn (\n\t\t<>\n\t\t\t<BlockControls>\n\t\t\t\t<AlignmentToolbar\n\t\t\t\t\tvalue={ alignmentValue }\n\t\t\t\t\tonChange={ ( value ) =>\n\t\t\t\t\t\tupdateField(\n\t\t\t\t\t\t\t'alignment',\n\t\t\t\t\t\t\t( value || alignmentValue ) as NonNullable< {{pascalCase}}Attributes[ 'alignment' ] >\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t/>\n\t\t\t</BlockControls>\n\t\t\t<InspectorControls>\n\t\t\t\t<InspectorFromManifest\n\t\t\t\t\tattributes={ attributes }\n\t\t\t\t\tfieldLookup={ editorFields }\n\t\t\t\t\tonChange={ updateField }\n\t\t\t\t\tpaths={ [ 'alignment', 'isVisible', 'showCount', 'buttonLabel' ] }\n\t\t\t\t\ttitle={ __( 'Persistence Settings', '{{textDomain}}' ) }\n\t\t\t\t>\n\t\t\t\t\t<TextControl\n\t\t\t\t\t\tlabel={ __( 'Resource Key', '{{textDomain}}' ) }\n\t\t\t\t\t\tvalue={ attributes.resourceKey ?? '' }\n\t\t\t\t\t\tonChange={ ( value ) => updateField( 'resourceKey', value ) }\n\t\t\t\t\t\thelp={ __( 'Stable persisted identifier used by the storage-backed counter endpoint.', '{{textDomain}}' ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<Notice status=\"info\" isDismissible={ false }>\n\t\t\t\t\t\t{ __( 'Storage mode: {{dataStorageMode}}', '{{textDomain}}' ) }\n\t\t\t\t\t</Notice>\n\t\t\t\t\t<Notice status=\"info\" isDismissible={ false }>\n\t\t\t\t\t\t{ __( 'Persistence policy: {{persistencePolicy}}', '{{textDomain}}' ) }\n\t\t\t\t\t\t<br />\n\t\t\t\t\t\t{ persistencePolicyDescription }\n\t\t\t\t\t</Notice>\n\t\t\t\t\t<Notice status=\"info\" isDismissible={ false }>\n\t\t\t\t\t\t{ __( '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}}' ) }\n\t\t\t\t\t</Notice>\n\t\t\t\t</InspectorFromManifest>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<PanelBody\n\t\t\t\t\t\ttitle={ __( 'Validation Errors', '{{textDomain}}' ) }\n\t\t\t\t\t\tinitialOpen\n\t\t\t\t\t>\n\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => (\n\t\t\t\t\t\t\t<Notice key={ index } status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t\t\t{ error }\n\t\t\t\t\t\t\t</Notice>\n\t\t\t\t\t\t) ) }\n\t\t\t\t\t</PanelBody>\n\t\t\t\t) }\n\t\t\t</InspectorControls>\n\t\t\t<div\n\t\t\t\t{ ...useBlockProps( {\n\t\t\t\t\tclassName: '{{cssClassName}}',\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\ttextAlign:\n\t\t\t\t\t\t\talignmentValue as NonNullable< {{pascalCase}}Attributes[ 'alignment' ] >,\n\t\t\t\t\t},\n\t\t\t\t} ) }\n\t\t\t>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"p\"\n\t\t\t\t\tvalue={ attributes.content }\n\t\t\t\t\tonChange={ ( value ) => updateField( 'content', value ) }\n\t\t\t\t\tplaceholder={ __( {{titleJson}} + ' persistence block', '{{textDomain}}' ) }\n\t\t\t\t/>\n\t\t\t\t<p className=\"{{cssClassName}}__meta\">\n\t\t\t\t\t{ __( 'Resource key:', '{{textDomain}}' ) } { attributes.resourceKey || '—' }\n\t\t\t\t</p>\n\t\t\t\t<p className=\"{{cssClassName}}__meta\">\n\t\t\t\t\t{ __( 'Storage mode:', '{{textDomain}}' ) } {{dataStorageMode}}\n\t\t\t\t</p>\n\t\t\t\t<p className=\"{{cssClassName}}__meta\">\n\t\t\t\t\t{ __( 'Persistence policy:', '{{textDomain}}' ) } {{persistencePolicy}}\n\t\t\t\t</p>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<Notice status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</Notice>\n\t\t\t\t) }\n\t\t\t</div>\n\t\t</>\n\t);\n}\n";
|
|
14
14
|
const PERSISTENCE_INDEX_TEMPLATE = "import { registerBlockType } from '@wordpress/blocks';\nimport type { BlockConfiguration } from '@wordpress/blocks';\nimport {\n\tbuildScaffoldBlockRegistration,\n\ttype ScaffoldBlockMetadata,\n} from '@wp-typia/block-runtime/blocks';\n\nimport Edit from './edit';\nimport Save from './save';\nimport metadata from './block.json';\nimport './style.scss';\n\nimport type { {{pascalCase}}Attributes } from './types';\n\nconst registration = buildScaffoldBlockRegistration<\n\tBlockConfiguration< {{pascalCase}}Attributes >\n>( metadata as ScaffoldBlockMetadata, {\n\tedit: Edit,\n\tsave: Save,\n} );\n\nregisterBlockType< {{pascalCase}}Attributes >(\n\tregistration.name,\n\tregistration.settings\n);\n";
|
|
15
15
|
const PERSISTENCE_SAVE_TEMPLATE = "export default function Save() {\n\t// This block is intentionally server-rendered. PHP bootstraps post context,\n\t// storage-backed state, and write-policy data before the frontend hydrates.\n\treturn null;\n}\n";
|
|
16
16
|
const PERSISTENCE_VALIDATORS_TEMPLATE = "import typia from 'typia';\nimport currentManifest from './typia.manifest.json';\nimport type {\n\t{{pascalCase}}Attributes,\n\t{{pascalCase}}ValidationResult,\n} from './types';\nimport { generateResourceKey } from '@wp-typia/block-runtime/identifiers';\nimport { createTemplateValidatorToolkit } from './validator-toolkit';\n\nconst scaffoldValidators = createTemplateValidatorToolkit< {{pascalCase}}Attributes >( {\n\tassert: typia.createAssert< {{pascalCase}}Attributes >(),\n\tclone: typia.misc.createClone< {{pascalCase}}Attributes >() as (\n\t\tvalue: {{pascalCase}}Attributes,\n\t) => {{pascalCase}}Attributes,\n\tis: typia.createIs< {{pascalCase}}Attributes >(),\n\tmanifest: currentManifest,\n\tprune: typia.misc.createPrune< {{pascalCase}}Attributes >(),\n\trandom: typia.createRandom< {{pascalCase}}Attributes >() as (\n\t\t...args: unknown[]\n\t) => {{pascalCase}}Attributes,\n\tfinalize: ( normalized ) => ( {\n\t\t...normalized,\n\t\tresourceKey:\n\t\t\tnormalized.resourceKey && normalized.resourceKey.length > 0\n\t\t\t\t? normalized.resourceKey\n\t\t\t\t: generateResourceKey( '{{slugKebabCase}}' ),\n\t} ),\n\tvalidate: typia.createValidate< {{pascalCase}}Attributes >(),\n} );\n\nexport const validators = scaffoldValidators.validators;\n\nexport const validate{{pascalCase}}Attributes =\n\tscaffoldValidators.validateAttributes as (\n\t\tattributes: unknown\n\t) => {{pascalCase}}ValidationResult;\n\nexport const sanitize{{pascalCase}}Attributes =\n\tscaffoldValidators.sanitizeAttributes as (\n\t\tattributes: Partial< {{pascalCase}}Attributes >\n\t) => {{pascalCase}}Attributes;\n\nexport const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;\n";
|
|
17
17
|
const PERSISTENCE_INTERACTIVITY_TEMPLATE = "import { getContext, store } from '@wordpress/interactivity';\nimport { generatePublicWriteRequestId } from '@wp-typia/block-runtime/identifiers';\n\nimport { fetchBootstrap, fetchState, writeState } from './api';\nimport type {\n\t{{pascalCase}}ClientState,\n\t{{pascalCase}}Context,\n\t{{pascalCase}}State,\n} from './types';\nimport type {\n\t{{pascalCase}}WriteStateRequest,\n} from './api-types';\n\nfunction hasExpiredPublicWriteToken(\n\texpiresAt?: number\n): boolean {\n\treturn (\n\t\ttypeof expiresAt === 'number' &&\n\t\texpiresAt > 0 &&\n\t\tDate.now() >= expiresAt * 1000\n\t);\n}\n\nfunction getWriteBlockedMessage(\n\tcontext: {{pascalCase}}Context\n): string {\n\treturn context.persistencePolicy === 'authenticated'\n\t\t? 'Sign in to persist this counter.'\n\t\t: 'Public writes are temporarily unavailable.';\n}\n\nconst BOOTSTRAP_MAX_ATTEMPTS = 3;\nconst BOOTSTRAP_RETRY_DELAYS_MS = [ 250, 500 ];\n\nasync function waitForBootstrapRetry( delayMs: number ): Promise< void > {\n\tawait new Promise( ( resolve ) => {\n\t\tsetTimeout( resolve, delayMs );\n\t} );\n}\n\nfunction getClientState(\n\tcontext: {{pascalCase}}Context\n): {{pascalCase}}ClientState {\n\tif ( context.client ) {\n\t\treturn context.client;\n\t}\n\n\tcontext.client = {\n\t\tbootstrapError: '',\n\t\twriteExpiry: 0,\n\t\twriteNonce: '',\n\t\twriteToken: '',\n\t};\n\n\treturn context.client;\n}\n\nfunction clearBootstrapError(\n\tcontext: {{pascalCase}}Context,\n\tclientState: {{pascalCase}}ClientState\n): void {\n\tif ( context.error === clientState.bootstrapError ) {\n\t\tcontext.error = '';\n\t}\n\tclientState.bootstrapError = '';\n}\n\nfunction setBootstrapError(\n\tcontext: {{pascalCase}}Context,\n\tclientState: {{pascalCase}}ClientState,\n\tmessage: string\n): void {\n\tclientState.bootstrapError = message;\n\tcontext.error = message;\n}\n\nconst { actions, state } = store( '{{slugKebabCase}}', {\n\tstate: {\n\t\tisHydrated: false,\n\t} as {{pascalCase}}State,\n\n\tactions: {\n\t\tasync loadState() {\n\t\t\tconst context = getContext< {{pascalCase}}Context >();\n\t\t\tif ( context.postId <= 0 || ! context.resourceKey ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcontext.isLoading = true;\n\t\t\tcontext.error = '';\n\n\t\t\ttry {\n\t\t\t\tconst result = await fetchState( {\n\t\t\t\t\tpostId: context.postId,\n\t\t\t\t\tresourceKey: context.resourceKey,\n\t\t\t\t}, {\n\t\t\t\t\ttransportTarget: 'frontend',\n\t\t\t\t} );\n\t\t\t\tif ( ! result.isValid || ! result.data ) {\n\t\t\t\t\tcontext.error = result.errors[ 0 ]?.expected ?? 'Unable to load counter';\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontext.count = result.data.count;\n\t\t\t} catch ( error ) {\n\t\t\t\tcontext.error =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown loading error';\n\t\t\t} finally {\n\t\t\t\tcontext.isLoading = false;\n\t\t\t}\n\t\t},\n\t\tasync loadBootstrap() {\n\t\t\tconst context = getContext< {{pascalCase}}Context >();\n\t\t\tconst clientState = getClientState( context );\n\t\t\tif ( context.postId <= 0 || ! context.resourceKey ) {\n\t\t\t\tcontext.bootstrapReady = true;\n\t\t\t\tcontext.canWrite = false;\n\t\t\t\tclientState.bootstrapError = '';\n\t\t\t\tclientState.writeExpiry = 0;\n\t\t\t\tclientState.writeNonce = '';\n\t\t\t\tclientState.writeToken = '';\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcontext.isBootstrapping = true;\n\n\t\t\tlet bootstrapSucceeded = false;\n\t\t\tlet lastBootstrapError =\n\t\t\t\t'Unable to initialize write access';\n\t\t\tconst includePublicWriteCredentials = {{isPublicPersistencePolicy}};\n\t\t\tconst includeRestNonce = {{isAuthenticatedPersistencePolicy}};\n\n\t\t\tfor ( let attempt = 1; attempt <= BOOTSTRAP_MAX_ATTEMPTS; attempt += 1 ) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await fetchBootstrap( {\n\t\t\t\t\t\tpostId: context.postId,\n\t\t\t\t\t\tresourceKey: context.resourceKey,\n\t\t\t\t\t}, {\n\t\t\t\t\t\ttransportTarget: 'frontend',\n\t\t\t\t\t} );\n\t\t\t\t\tif ( ! result.isValid || ! result.data ) {\n\t\t\t\t\t\tlastBootstrapError =\n\t\t\t\t\t\t\tresult.errors[ 0 ]?.expected ??\n\t\t\t\t\t\t\t'Unable to initialize write access';\n\t\t\t\t\t\tif ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {\n\t\t\t\t\t\t\tawait waitForBootstrapRetry(\n\t\t\t\t\t\t\t\tBOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tclientState.writeExpiry =\n\t\t\t\t\t\tincludePublicWriteCredentials &&\n\t\t\t\t\t\t'publicWriteExpiresAt' in result.data &&\n\t\t\t\t\t\ttypeof result.data.publicWriteExpiresAt === 'number' &&\n\t\t\t\t\t\tresult.data.publicWriteExpiresAt > 0\n\t\t\t\t\t\t\t? result.data.publicWriteExpiresAt\n\t\t\t\t\t\t\t: 0;\n\t\t\t\t\tclientState.writeToken =\n\t\t\t\t\t\tincludePublicWriteCredentials &&\n\t\t\t\t\t\t'publicWriteToken' in result.data &&\n\t\t\t\t\t\ttypeof result.data.publicWriteToken === 'string' &&\n\t\t\t\t\t\tresult.data.publicWriteToken.length > 0\n\t\t\t\t\t\t\t? result.data.publicWriteToken\n\t\t\t\t\t\t\t: '';\n\t\t\t\t\tclientState.writeNonce =\n\t\t\t\t\t\tincludeRestNonce &&\n\t\t\t\t\t\t'restNonce' in result.data &&\n\t\t\t\t\t\ttypeof result.data.restNonce === 'string' &&\n\t\t\t\t\t\tresult.data.restNonce.length > 0\n\t\t\t\t\t\t\t? result.data.restNonce\n\t\t\t\t\t\t\t: '';\n\t\t\t\t\tcontext.bootstrapReady = true;\n\t\t\t\t\tcontext.canWrite =\n\t\t\t\t\t\tresult.data.canWrite === true &&\n\t\t\t\t\t\t(\n\t\t\t\t\t\t\tcontext.persistencePolicy === 'authenticated'\n\t\t\t\t\t\t\t\t? clientState.writeNonce.length > 0\n\t\t\t\t\t\t\t\t: clientState.writeToken.length > 0 &&\n\t\t\t\t\t\t\t\t\t! hasExpiredPublicWriteToken( clientState.writeExpiry )\n\t\t\t\t\t\t);\n\t\t\t\t\tclearBootstrapError( context, clientState );\n\t\t\t\t\tbootstrapSucceeded = true;\n\t\t\t\t\tbreak;\n\t\t\t\t} catch ( error ) {\n\t\t\t\t\tlastBootstrapError =\n\t\t\t\t\t\terror instanceof Error ? error.message : 'Unknown bootstrap error';\n\t\t\t\t\tif ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {\n\t\t\t\t\t\tawait waitForBootstrapRetry(\n\t\t\t\t\t\t\tBOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750\n\t\t\t\t\t\t);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( ! bootstrapSucceeded ) {\n\t\t\t\tcontext.bootstrapReady = false;\n\t\t\t\tcontext.canWrite = false;\n\t\t\t\tclientState.writeExpiry = 0;\n\t\t\t\tclientState.writeNonce = '';\n\t\t\t\tclientState.writeToken = '';\n\t\t\t\tsetBootstrapError( context, clientState, lastBootstrapError );\n\t\t\t}\n\t\t\tcontext.isBootstrapping = false;\n\t\t},\n\t\tasync increment() {\n\t\t\tconst context = getContext< {{pascalCase}}Context >();\n\t\t\tconst clientState = getClientState( context );\n\t\t\tif ( context.postId <= 0 || ! context.resourceKey ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( ! context.bootstrapReady ) {\n\t\t\t\tawait actions.loadBootstrap();\n\t\t\t}\n\t\t\tif ( ! context.bootstrapReady ) {\n\t\t\t\tcontext.error = 'Write access is still initializing.';\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tcontext.persistencePolicy === 'public' &&\n\t\t\t\thasExpiredPublicWriteToken( clientState.writeExpiry )\n\t\t\t) {\n\t\t\t\tawait actions.loadBootstrap();\n\t\t\t}\n\t\t\tif (\n\t\t\t\tcontext.persistencePolicy === 'public' &&\n\t\t\t\thasExpiredPublicWriteToken( clientState.writeExpiry )\n\t\t\t) {\n\t\t\t\tcontext.canWrite = false;\n\t\t\t\tcontext.error = getWriteBlockedMessage( context );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( ! context.canWrite ) {\n\t\t\t\tcontext.error = getWriteBlockedMessage( context );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcontext.isSaving = true;\n\t\t\tcontext.error = '';\n\n\t\t\ttry {\n\t\t\t\tconst request = {\n\t\t\t\t\tdelta: 1,\n\t\t\t\t\tpostId: context.postId,\n\t\t\t\t\tresourceKey: context.resourceKey,\n\t\t\t\t} as {{pascalCase}}WriteStateRequest;\n\t\t\t\tif ( {{isPublicPersistencePolicy}} ) {\n\t\t\t\t\trequest.publicWriteRequestId =\n\t\t\t\t\t\tgeneratePublicWriteRequestId() as {{pascalCase}}WriteStateRequest[ 'publicWriteRequestId' ];\n\t\t\t\t\tif ( clientState.writeToken.length > 0 ) {\n\t\t\t\t\t\trequest.publicWriteToken =\n\t\t\t\t\t\t\tclientState.writeToken as {{pascalCase}}WriteStateRequest[ 'publicWriteToken' ];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst result = await writeState( request, {\n\t\t\t\t\trestNonce:\n\t\t\t\t\t\tclientState.writeNonce.length > 0\n\t\t\t\t\t\t\t? clientState.writeNonce\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\ttransportTarget: 'frontend',\n\t\t\t\t} );\n\t\t\t\tif ( ! result.isValid || ! result.data ) {\n\t\t\t\t\tcontext.error = result.errors[ 0 ]?.expected ?? 'Unable to update counter';\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontext.count = result.data.count;\n\t\t\t\tcontext.storage = result.data.storage;\n\t\t\t} catch ( error ) {\n\t\t\t\tcontext.error =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown update error';\n\t\t\t} finally {\n\t\t\t\tcontext.isSaving = false;\n\t\t\t}\n\t\t},\n\t},\n\n\tcallbacks: {\n\t\tinit() {\n\t\t\tconst context = getContext< {{pascalCase}}Context >();\n\t\t\tcontext.client = {\n\t\t\t\tbootstrapError: '',\n\t\t\t\twriteExpiry: 0,\n\t\t\t\twriteNonce: '',\n\t\t\t\twriteToken: '',\n\t\t\t};\n\t\t\tcontext.bootstrapReady = false;\n\t\t\tcontext.canWrite = false;\n\t\t\tcontext.count = 0;\n\t\t\tcontext.error = '';\n\t\t\tcontext.isBootstrapping = false;\n\t\t\tcontext.isLoading = false;\n\t\t\tcontext.isSaving = false;\n\t\t},\n\t\tmounted() {\n\t\t\tstate.isHydrated = true;\n\t\t\tif ( typeof document !== 'undefined' ) {\n\t\t\t\tdocument.documentElement.dataset[ '{{slugCamelCase}}Hydrated' ] = 'true';\n\t\t\t}\n\t\t\tvoid Promise.allSettled( [\n\t\t\t\tactions.loadState(),\n\t\t\t\tactions.loadBootstrap(),\n\t\t\t] );\n\t\t},\n\t},\n} );\n";
|
|
18
|
-
const COMPOUND_PARENT_EDIT_TEMPLATE = "import { __ } from '@wordpress/i18n';\nimport {\n\tInspectorControls,\n\tInnerBlocks,\n\tRichText,\n\tuseBlockProps,\n} from '@wordpress/block-editor';\nimport { Notice, PanelBody, ToggleControl } from '@wordpress/components';\n\nimport {\n\tALLOWED_CHILD_BLOCKS,\n\tDEFAULT_CHILD_TEMPLATE,\n} from './children';\nimport { useTypiaValidation } from './hooks';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n\tcreateAttributeUpdater,\n\tvalidate{{pascalCase}}Attributes,\n} from './validators';\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}:
|
|
18
|
+
const COMPOUND_PARENT_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wordpress/blocks';\nimport { __ } from '@wordpress/i18n';\nimport {\n\tInspectorControls,\n\tInnerBlocks,\n\tRichText,\n\tuseBlockProps,\n} from '@wordpress/block-editor';\nimport { Notice, PanelBody, ToggleControl } from '@wordpress/components';\n\nimport {\n\tALLOWED_CHILD_BLOCKS,\n\tDEFAULT_CHILD_TEMPLATE,\n} from './children';\nimport { useTypiaValidation } from './hooks';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n\tcreateAttributeUpdater,\n\tvalidate{{pascalCase}}Attributes,\n} from './validators';\n\ntype EditProps = BlockEditProps< {{pascalCase}}Attributes >;\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}: EditProps ) {\n\tconst { errorMessages, isValid } = useTypiaValidation(\n\t\tattributes,\n\t\tvalidate{{pascalCase}}Attributes\n\t);\n\tconst updateAttribute = createAttributeUpdater( attributes, setAttributes );\n\tconst blockProps = useBlockProps( {\n\t\tclassName: '{{cssClassName}}',\n\t} );\n\n\treturn (\n\t\t<>\n\t\t\t<InspectorControls>\n\t\t\t\t<PanelBody title={ __( 'Compound Settings', '{{textDomain}}' ) }>\n\t\t\t\t\t<ToggleControl\n\t\t\t\t\t\tlabel={ __( 'Show dividers between items', '{{textDomain}}' ) }\n\t\t\t\t\t\tchecked={ attributes.showDividers ?? true }\n\t\t\t\t\t\tonChange={ ( value ) => updateAttribute( 'showDividers', value ) }\n\t\t\t\t\t/>\n\t\t\t\t</PanelBody>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<PanelBody title={ __( 'Validation Errors', '{{textDomain}}' ) } initialOpen>\n\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => (\n\t\t\t\t\t\t\t<Notice key={ index } status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t\t\t{ error }\n\t\t\t\t\t\t\t</Notice>\n\t\t\t\t\t\t) ) }\n\t\t\t\t\t</PanelBody>\n\t\t\t\t) }\n\t\t\t</InspectorControls>\n\t\t\t<div { ...blockProps }>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"h3\"\n\t\t\t\t\tclassName=\"{{cssClassName}}__heading\"\n\t\t\t\t\tvalue={ attributes.heading }\n\t\t\t\t\tonChange={ ( heading ) => updateAttribute( 'heading', heading ) }\n\t\t\t\t\tplaceholder={ __( {{titleJson}}, '{{textDomain}}' ) }\n\t\t\t\t/>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"p\"\n\t\t\t\t\tclassName=\"{{cssClassName}}__intro\"\n\t\t\t\t\tvalue={ attributes.intro ?? '' }\n\t\t\t\t\tonChange={ ( intro ) => updateAttribute( 'intro', intro ) }\n\t\t\t\t\tplaceholder={ __(\n\t\t\t\t\t\t'Add and reorder internal items inside this compound block.',\n\t\t\t\t\t\t'{{textDomain}}'\n\t\t\t\t\t) }\n\t\t\t\t/>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<Notice status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</Notice>\n\t\t\t\t) }\n\t\t\t\t<div className=\"{{cssClassName}}__items\">\n\t\t\t\t\t<InnerBlocks\n\t\t\t\t\t\tallowedBlocks={ ALLOWED_CHILD_BLOCKS }\n\t\t\t\t\t\trenderAppender={ InnerBlocks.ButtonBlockAppender }\n\t\t\t\t\t\ttemplate={ DEFAULT_CHILD_TEMPLATE }\n\t\t\t\t\t\ttemplateLock={ false }\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n";
|
|
19
19
|
const COMPOUND_PARENT_SAVE_TEMPLATE = "import { InnerBlocks, RichText, useBlockProps } from '@wordpress/block-editor';\n\nimport type { {{pascalCase}}Attributes } from './types';\n\nexport default function Save( {\n\tattributes,\n}: {\n\tattributes: {{pascalCase}}Attributes;\n} ) {\n\treturn (\n\t\t<div\n\t\t\t{ ...useBlockProps.save( {\n\t\t\t\tclassName: '{{cssClassName}}',\n\t\t\t\t'data-show-dividers': ( attributes.showDividers ?? true ) ? 'true' : 'false',\n\t\t\t} ) }\n\t\t>\n\t\t\t<RichText.Content\n\t\t\t\ttagName=\"h3\"\n\t\t\t\tclassName=\"{{cssClassName}}__heading\"\n\t\t\t\tvalue={ attributes.heading }\n\t\t\t/>\n\t\t\t<RichText.Content\n\t\t\t\ttagName=\"p\"\n\t\t\t\tclassName=\"{{cssClassName}}__intro\"\n\t\t\t\tvalue={ attributes.intro ?? '' }\n\t\t\t/>\n\t\t\t<div className=\"{{cssClassName}}__items\">\n\t\t\t\t<InnerBlocks.Content />\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n";
|
|
20
20
|
const COMPOUND_PARENT_INDEX_TEMPLATE = "import { registerBlockType } from '@wordpress/blocks';\nimport type { BlockConfiguration } from '@wordpress/blocks';\nimport {\n\tbuildScaffoldBlockRegistration,\n\ttype ScaffoldBlockMetadata,\n} from '@wp-typia/block-runtime/blocks';\n\nimport Edit from './edit';\nimport Save from './save';\nimport metadata from './block.json';\nimport './style.scss';\n\nimport type { {{pascalCase}}Attributes } from './types';\n\nconst registration = buildScaffoldBlockRegistration<\n\tBlockConfiguration< {{pascalCase}}Attributes >\n>( metadata as ScaffoldBlockMetadata, {\n\tedit: Edit,\n\tsave: Save,\n} );\n\nregisterBlockType< {{pascalCase}}Attributes >(\n\tregistration.name,\n\tregistration.settings\n);\n";
|
|
21
21
|
const COMPOUND_LOCAL_HOOKS_TEMPLATE = "export {\n\tformatValidationError,\n\tformatValidationErrors,\n\tuseTypiaValidation,\n} from '../../hooks';\n\nexport type {\n\tTypiaValidationError,\n\tValidationResult,\n\tValidationState,\n} from '../../hooks';\n";
|
|
22
22
|
const COMPOUND_PARENT_VALIDATORS_TEMPLATE = "import typia from 'typia';\nimport currentManifest from './typia.manifest.json';\nimport type {\n\t{{pascalCase}}Attributes,\n\t{{pascalCase}}ValidationResult,\n} from './types';\nimport { createTemplateValidatorToolkit } from '../../validator-toolkit';\n\nconst scaffoldValidators = createTemplateValidatorToolkit< {{pascalCase}}Attributes >( {\n\tassert: typia.createAssert< {{pascalCase}}Attributes >(),\n\tclone: typia.misc.createClone< {{pascalCase}}Attributes >() as (\n\t\tvalue: {{pascalCase}}Attributes,\n\t) => {{pascalCase}}Attributes,\n\tis: typia.createIs< {{pascalCase}}Attributes >(),\n\tmanifest: currentManifest,\n\tprune: typia.misc.createPrune< {{pascalCase}}Attributes >(),\n\trandom: typia.createRandom< {{pascalCase}}Attributes >() as (\n\t\t...args: unknown[]\n\t) => {{pascalCase}}Attributes,\n\tvalidate: typia.createValidate< {{pascalCase}}Attributes >(),\n} );\n\nexport const validate{{pascalCase}}Attributes =\n\tscaffoldValidators.validateAttributes as (\n\t\tattributes: unknown\n\t) => {{pascalCase}}ValidationResult;\n\nexport const validators = scaffoldValidators.validators;\n\nexport const sanitize{{pascalCase}}Attributes =\n\tscaffoldValidators.sanitizeAttributes as (\n\t\tattributes: Partial< {{pascalCase}}Attributes >\n\t) => {{pascalCase}}Attributes;\n\nexport const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;\n";
|
|
23
23
|
const COMPOUND_CHILDREN_TEMPLATE = "import type { Template } from '@wordpress/blocks';\n\nexport const DEFAULT_CHILD_BLOCK_NAME = '{{namespace}}/{{slugKebabCase}}-item';\n\nexport const ALLOWED_CHILD_BLOCKS = [\n\tDEFAULT_CHILD_BLOCK_NAME,\n\t// add-child: insert new allowed child block names here\n];\n\nexport const DEFAULT_CHILD_TEMPLATE: Template[] = [\n\t[\n\t\tDEFAULT_CHILD_BLOCK_NAME,\n\t\t{\n\t\t\tbody: 'Add supporting details for the first internal item.',\n\t\t\ttitle: 'First Item',\n\t\t},\n\t],\n\t[\n\t\tDEFAULT_CHILD_BLOCK_NAME,\n\t\t{\n\t\t\tbody: 'Add supporting details for the second internal item.',\n\t\t\ttitle: 'Second Item',\n\t\t},\n\t],\n];\n";
|
|
24
|
-
const COMPOUND_CHILD_EDIT_TEMPLATE = "import { RichText, useBlockProps } from '@wordpress/block-editor';\nimport { Notice } from '@wordpress/components';\nimport { __ } from '@wordpress/i18n';\n\nimport { useTypiaValidation } from './hooks';\nimport type { {{pascalCase}}ItemAttributes } from './types';\nimport {\n\tcreateAttributeUpdater,\n\tvalidate{{pascalCase}}ItemAttributes,\n} from './validators';\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}:
|
|
24
|
+
const COMPOUND_CHILD_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wordpress/blocks';\nimport { RichText, useBlockProps } from '@wordpress/block-editor';\nimport { Notice } from '@wordpress/components';\nimport { __ } from '@wordpress/i18n';\n\nimport { useTypiaValidation } from './hooks';\nimport type { {{pascalCase}}ItemAttributes } from './types';\nimport {\n\tcreateAttributeUpdater,\n\tvalidate{{pascalCase}}ItemAttributes,\n} from './validators';\n\ntype EditProps = BlockEditProps< {{pascalCase}}ItemAttributes >;\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}: EditProps ) {\n\tconst updateAttribute = createAttributeUpdater( attributes, setAttributes );\n\tconst { errorMessages, isValid } = useTypiaValidation(\n\t\tattributes,\n\t\tvalidate{{pascalCase}}ItemAttributes\n\t);\n\n\treturn (\n\t\t<div { ...useBlockProps( { className: '{{compoundChildCssClassName}}' } ) }>\n\t\t\t<RichText\n\t\t\t\ttagName=\"h4\"\n\t\t\t\tclassName=\"{{compoundChildCssClassName}}__title\"\n\t\t\t\tvalue={ attributes.title ?? '' }\n\t\t\t\tonChange={ ( title ) => updateAttribute( 'title', title ) }\n\t\t\t\tplaceholder={ __( {{compoundChildTitleJson}}, '{{textDomain}}' ) }\n\t\t\t/>\n\t\t\t<RichText\n\t\t\t\ttagName=\"p\"\n\t\t\t\tclassName=\"{{compoundChildCssClassName}}__body\"\n\t\t\t\tvalue={ attributes.body ?? '' }\n\t\t\t\tonChange={ ( body ) => updateAttribute( 'body', body ) }\n\t\t\t\tplaceholder={ __( 'Add supporting details for this internal item.', '{{textDomain}}' ) }\n\t\t\t/>\n\t\t\t{ ! isValid && (\n\t\t\t\t<Notice status=\"error\" isDismissible={ false }>\n\t\t\t\t\t<ul>\n\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }\n\t\t\t\t\t</ul>\n\t\t\t\t</Notice>\n\t\t\t) }\n\t\t</div>\n\t);\n}\n";
|
|
25
25
|
const COMPOUND_CHILD_SAVE_TEMPLATE = "import { RichText, useBlockProps } from '@wordpress/block-editor';\n\nimport type { {{pascalCase}}ItemAttributes } from './types';\n\nexport default function Save( {\n\tattributes,\n}: {\n\tattributes: {{pascalCase}}ItemAttributes;\n} ) {\n\treturn (\n\t\t<div { ...useBlockProps.save( { className: '{{compoundChildCssClassName}}' } ) }>\n\t\t\t<RichText.Content\n\t\t\t\ttagName=\"h4\"\n\t\t\t\tclassName=\"{{compoundChildCssClassName}}__title\"\n\t\t\t\tvalue={ attributes.title }\n\t\t\t/>\n\t\t\t<RichText.Content\n\t\t\t\ttagName=\"p\"\n\t\t\t\tclassName=\"{{compoundChildCssClassName}}__body\"\n\t\t\t\tvalue={ attributes.body }\n\t\t\t/>\n\t\t</div>\n\t);\n}\n";
|
|
26
26
|
const COMPOUND_CHILD_INDEX_TEMPLATE = "import { registerBlockType } from '@wordpress/blocks';\nimport type { BlockConfiguration } from '@wordpress/blocks';\nimport {\n\tbuildScaffoldBlockRegistration,\n\ttype ScaffoldBlockMetadata,\n} from '@wp-typia/block-runtime/blocks';\n\nimport Edit from './edit';\nimport Save from './save';\nimport metadata from './block.json';\nimport '../{{slugKebabCase}}/style.scss';\n\nimport type { {{pascalCase}}ItemAttributes } from './types';\n\nconst registration = buildScaffoldBlockRegistration<\n\tBlockConfiguration< {{pascalCase}}ItemAttributes >\n>( metadata as ScaffoldBlockMetadata, {\n\tedit: Edit,\n\tsave: Save,\n} );\n\nregisterBlockType< {{pascalCase}}ItemAttributes >(\n\tregistration.name,\n\tregistration.settings\n);\n";
|
|
27
27
|
const COMPOUND_CHILD_VALIDATORS_TEMPLATE = "import typia from 'typia';\nimport currentManifest from './typia.manifest.json';\nimport type {\n\t{{pascalCase}}ItemAttributes,\n\t{{pascalCase}}ItemValidationResult,\n} from './types';\nimport { createTemplateValidatorToolkit } from '../../validator-toolkit';\n\nconst scaffoldValidators = createTemplateValidatorToolkit< {{pascalCase}}ItemAttributes >( {\n\tassert: typia.createAssert< {{pascalCase}}ItemAttributes >(),\n\tclone: typia.misc.createClone< {{pascalCase}}ItemAttributes >() as (\n\t\tvalue: {{pascalCase}}ItemAttributes,\n\t) => {{pascalCase}}ItemAttributes,\n\tis: typia.createIs< {{pascalCase}}ItemAttributes >(),\n\tmanifest: currentManifest,\n\tprune: typia.misc.createPrune< {{pascalCase}}ItemAttributes >(),\n\trandom: typia.createRandom< {{pascalCase}}ItemAttributes >() as (\n\t\t...args: unknown[]\n\t) => {{pascalCase}}ItemAttributes,\n\tvalidate: typia.createValidate< {{pascalCase}}ItemAttributes >(),\n} );\n\nexport const validate{{pascalCase}}ItemAttributes =\n\tscaffoldValidators.validateAttributes as (\n\t\tattributes: unknown\n\t) => {{pascalCase}}ItemValidationResult;\n\nexport const validators = scaffoldValidators.validators;\n\nexport const sanitize{{pascalCase}}ItemAttributes =\n\tscaffoldValidators.sanitizeAttributes as (\n\t\tattributes: Partial< {{pascalCase}}ItemAttributes >\n\t) => {{pascalCase}}ItemAttributes;\n\nexport const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;\n";
|
|
28
|
-
const COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE = "import { __ } from '@wordpress/i18n';\nimport {\n\tInspectorControls,\n\tInnerBlocks,\n\tRichText,\n\tuseBlockProps,\n} from '@wordpress/block-editor';\nimport {\n\tNotice,\n\tPanelBody,\n\tTextControl,\n\tToggleControl,\n} from '@wordpress/components';\n\nimport {\n\tALLOWED_CHILD_BLOCKS,\n\tDEFAULT_CHILD_TEMPLATE,\n} from './children';\nimport { useTypiaValidation } from './hooks';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n\tcreateAttributeUpdater,\n\tvalidate{{pascalCase}}Attributes,\n} from './validators';\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}:
|
|
28
|
+
const COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wordpress/blocks';\nimport { __ } from '@wordpress/i18n';\nimport {\n\tInspectorControls,\n\tInnerBlocks,\n\tRichText,\n\tuseBlockProps,\n} from '@wordpress/block-editor';\nimport {\n\tNotice,\n\tPanelBody,\n\tTextControl,\n\tToggleControl,\n} from '@wordpress/components';\n\nimport {\n\tALLOWED_CHILD_BLOCKS,\n\tDEFAULT_CHILD_TEMPLATE,\n} from './children';\nimport { useTypiaValidation } from './hooks';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n\tcreateAttributeUpdater,\n\tvalidate{{pascalCase}}Attributes,\n} from './validators';\n\ntype EditProps = BlockEditProps< {{pascalCase}}Attributes >;\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}: EditProps ) {\n\tconst { errorMessages, isValid } = useTypiaValidation(\n\t\tattributes,\n\t\tvalidate{{pascalCase}}Attributes\n\t);\n\tconst updateAttribute = createAttributeUpdater( attributes, setAttributes );\n\tconst blockProps = useBlockProps( {\n\t\tclassName: '{{cssClassName}}',\n\t} );\n\n\treturn (\n\t\t<>\n\t\t\t<InspectorControls>\n\t\t\t\t<PanelBody title={ __( 'Compound Settings', '{{textDomain}}' ) }>\n\t\t\t\t\t<ToggleControl\n\t\t\t\t\t\tlabel={ __( 'Show dividers between items', '{{textDomain}}' ) }\n\t\t\t\t\t\tchecked={ attributes.showDividers ?? true }\n\t\t\t\t\t\tonChange={ ( value ) => updateAttribute( 'showDividers', value ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<ToggleControl\n\t\t\t\t\t\tlabel={ __( 'Show persisted count', '{{textDomain}}' ) }\n\t\t\t\t\t\tchecked={ attributes.showCount ?? true }\n\t\t\t\t\t\tonChange={ ( value ) => updateAttribute( 'showCount', value ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<TextControl\n\t\t\t\t\t\tlabel={ __( 'Button label', '{{textDomain}}' ) }\n\t\t\t\t\t\tvalue={ attributes.buttonLabel ?? 'Persist Count' }\n\t\t\t\t\t\tonChange={ ( buttonLabel ) => updateAttribute( 'buttonLabel', buttonLabel ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<TextControl\n\t\t\t\t\t\tlabel={ __( 'Resource key', '{{textDomain}}' ) }\n\t\t\t\t\t\tvalue={ attributes.resourceKey ?? '' }\n\t\t\t\t\t\tonChange={ ( resourceKey ) => updateAttribute( 'resourceKey', resourceKey ) }\n\t\t\t\t\t\thelp={ __( 'Stable key used by the persisted counter endpoint.', '{{textDomain}}' ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<Notice status=\"info\" isDismissible={ false }>\n\t\t\t\t\t\t{ __( 'Storage mode: {{dataStorageMode}}', '{{textDomain}}' ) }\n\t\t\t\t\t</Notice>\n\t\t\t\t\t<Notice status=\"info\" isDismissible={ false }>\n\t\t\t\t\t\t{ __( 'Persistence policy: {{persistencePolicy}}', '{{textDomain}}' ) }\n\t\t\t\t\t</Notice>\n\t\t\t\t</PanelBody>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<PanelBody title={ __( 'Validation Errors', '{{textDomain}}' ) } initialOpen>\n\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => (\n\t\t\t\t\t\t\t<Notice key={ index } status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t\t\t{ error }\n\t\t\t\t\t\t\t</Notice>\n\t\t\t\t\t\t) ) }\n\t\t\t\t\t</PanelBody>\n\t\t\t\t) }\n\t\t\t</InspectorControls>\n\t\t\t<div { ...blockProps }>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"h3\"\n\t\t\t\t\tclassName=\"{{cssClassName}}__heading\"\n\t\t\t\t\tvalue={ attributes.heading }\n\t\t\t\t\tonChange={ ( heading ) => updateAttribute( 'heading', heading ) }\n\t\t\t\t\tplaceholder={ __( {{titleJson}}, '{{textDomain}}' ) }\n\t\t\t\t/>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"p\"\n\t\t\t\t\tclassName=\"{{cssClassName}}__intro\"\n\t\t\t\t\tvalue={ attributes.intro ?? '' }\n\t\t\t\t\tonChange={ ( intro ) => updateAttribute( 'intro', intro ) }\n\t\t\t\t\tplaceholder={ __(\n\t\t\t\t\t\t'Add and reorder internal items inside this compound block.',\n\t\t\t\t\t\t'{{textDomain}}'\n\t\t\t\t\t) }\n\t\t\t\t/>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<Notice status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</Notice>\n\t\t\t\t) }\n\t\t\t\t<p className=\"{{cssClassName}}__meta\">\n\t\t\t\t\t{ __( 'Resource key:', '{{textDomain}}' ) } { attributes.resourceKey || '—' }\n\t\t\t\t</p>\n\t\t\t\t<div className=\"{{cssClassName}}__items\">\n\t\t\t\t\t<InnerBlocks\n\t\t\t\t\t\tallowedBlocks={ ALLOWED_CHILD_BLOCKS }\n\t\t\t\t\t\trenderAppender={ InnerBlocks.ButtonBlockAppender }\n\t\t\t\t\t\ttemplate={ DEFAULT_CHILD_TEMPLATE }\n\t\t\t\t\t\ttemplateLock={ false }\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n";
|
|
29
29
|
const COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE = "export default function Save() {\n\treturn null;\n}\n";
|
|
30
30
|
const COMPOUND_PERSISTENCE_PARENT_VALIDATORS_TEMPLATE = "import typia from 'typia';\nimport currentManifest from './typia.manifest.json';\nimport type {\n\t{{pascalCase}}Attributes,\n\t{{pascalCase}}ValidationResult,\n} from './types';\nimport { generateResourceKey } from '@wp-typia/block-runtime/identifiers';\nimport { createTemplateValidatorToolkit } from '../../validator-toolkit';\n\nconst scaffoldValidators = createTemplateValidatorToolkit< {{pascalCase}}Attributes >( {\n\tassert: typia.createAssert< {{pascalCase}}Attributes >(),\n\tclone: typia.misc.createClone< {{pascalCase}}Attributes >() as (\n\t\tvalue: {{pascalCase}}Attributes,\n\t) => {{pascalCase}}Attributes,\n\tis: typia.createIs< {{pascalCase}}Attributes >(),\n\tmanifest: currentManifest,\n\tprune: typia.misc.createPrune< {{pascalCase}}Attributes >(),\n\trandom: typia.createRandom< {{pascalCase}}Attributes >() as (\n\t\t...args: unknown[]\n\t) => {{pascalCase}}Attributes,\n\tfinalize: ( normalized ) => ( {\n\t\t...normalized,\n\t\tresourceKey:\n\t\t\tnormalized.resourceKey && normalized.resourceKey.length > 0\n\t\t\t\t? normalized.resourceKey\n\t\t\t\t: generateResourceKey( '{{slugKebabCase}}' ),\n\t} ),\n\tvalidate: typia.createValidate< {{pascalCase}}Attributes >(),\n} );\n\nexport const validators = scaffoldValidators.validators;\n\nexport const validate{{pascalCase}}Attributes =\n\tscaffoldValidators.validateAttributes as (\n\t\tattributes: unknown\n\t) => {{pascalCase}}ValidationResult;\n\nexport const sanitize{{pascalCase}}Attributes =\n\tscaffoldValidators.sanitizeAttributes as (\n\t\tattributes: Partial< {{pascalCase}}Attributes >\n\t) => {{pascalCase}}Attributes;\n\nexport const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;\n";
|
|
31
31
|
const COMPOUND_PERSISTENCE_PARENT_INTERACTIVITY_TEMPLATE = "import { getContext, store } from '@wordpress/interactivity';\nimport { generatePublicWriteRequestId } from '@wp-typia/block-runtime/identifiers';\n\nimport { fetchBootstrap, fetchState, writeState } from './api';\nimport type {\n\t{{pascalCase}}ClientState,\n\t{{pascalCase}}Context,\n\t{{pascalCase}}State,\n} from './types';\n\nfunction hasExpiredPublicWriteToken(\n\texpiresAt?: number\n): boolean {\n\treturn (\n\t\ttypeof expiresAt === 'number' &&\n\t\texpiresAt > 0 &&\n\t\tDate.now() >= expiresAt * 1000\n\t);\n}\n\nfunction getWriteBlockedMessage(\n\tcontext: {{pascalCase}}Context\n): string {\n\treturn context.persistencePolicy === 'authenticated'\n\t\t? 'Sign in to persist this counter.'\n\t\t: 'Public writes are temporarily unavailable.';\n}\n\nconst BOOTSTRAP_MAX_ATTEMPTS = 3;\nconst BOOTSTRAP_RETRY_DELAYS_MS = [ 250, 500 ];\n\nasync function waitForBootstrapRetry( delayMs: number ): Promise< void > {\n\tawait new Promise( ( resolve ) => {\n\t\tsetTimeout( resolve, delayMs );\n\t} );\n}\n\nfunction getClientState(\n\tcontext: {{pascalCase}}Context\n): {{pascalCase}}ClientState {\n\tif ( context.client ) {\n\t\treturn context.client;\n\t}\n\n\tcontext.client = {\n\t\tbootstrapError: '',\n\t\twriteExpiry: 0,\n\t\twriteNonce: '',\n\t\twriteToken: '',\n\t};\n\n\treturn context.client;\n}\n\nfunction clearBootstrapError(\n\tcontext: {{pascalCase}}Context,\n\tclientState: {{pascalCase}}ClientState\n): void {\n\tif ( context.error === clientState.bootstrapError ) {\n\t\tcontext.error = '';\n\t}\n\tclientState.bootstrapError = '';\n}\n\nfunction setBootstrapError(\n\tcontext: {{pascalCase}}Context,\n\tclientState: {{pascalCase}}ClientState,\n\tmessage: string\n): void {\n\tclientState.bootstrapError = message;\n\tcontext.error = message;\n}\n\nconst { actions, state } = store( '{{slugKebabCase}}', {\n\tstate: {\n\t\tisHydrated: false,\n\t} as {{pascalCase}}State,\n\n\tactions: {\n\t\tasync loadState() {\n\t\t\tconst context = getContext< {{pascalCase}}Context >();\n\t\t\tif ( context.postId <= 0 || ! context.resourceKey ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcontext.isLoading = true;\n\t\t\tcontext.error = '';\n\n\t\t\ttry {\n\t\t\t\tconst result = await fetchState( {\n\t\t\t\t\tpostId: context.postId,\n\t\t\t\t\tresourceKey: context.resourceKey,\n\t\t\t\t}, {\n\t\t\t\t\ttransportTarget: 'frontend',\n\t\t\t\t} );\n\t\t\t\tif ( ! result.isValid || ! result.data ) {\n\t\t\t\t\tcontext.error = result.errors[ 0 ]?.expected ?? 'Unable to load counter';\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontext.count = result.data.count;\n\t\t\t} catch ( error ) {\n\t\t\t\tcontext.error =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown loading error';\n\t\t\t} finally {\n\t\t\t\tcontext.isLoading = false;\n\t\t\t}\n\t\t},\n\t\tasync loadBootstrap() {\n\t\t\tconst context = getContext< {{pascalCase}}Context >();\n\t\t\tconst clientState = getClientState( context );\n\t\t\tif ( context.postId <= 0 || ! context.resourceKey ) {\n\t\t\t\tcontext.bootstrapReady = true;\n\t\t\t\tcontext.canWrite = false;\n\t\t\t\tclientState.bootstrapError = '';\n\t\t\t\tclientState.writeExpiry = 0;\n\t\t\t\tclientState.writeNonce = '';\n\t\t\t\tclientState.writeToken = '';\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcontext.isBootstrapping = true;\n\n\t\t\tlet bootstrapSucceeded = false;\n\t\t\tlet lastBootstrapError =\n\t\t\t\t'Unable to initialize write access';\n\t\t\tconst includePublicWriteCredentials = {{isPublicPersistencePolicy}};\n\t\t\tconst includeRestNonce = {{isAuthenticatedPersistencePolicy}};\n\n\t\t\tfor ( let attempt = 1; attempt <= BOOTSTRAP_MAX_ATTEMPTS; attempt += 1 ) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await fetchBootstrap( {\n\t\t\t\t\t\tpostId: context.postId,\n\t\t\t\t\t\tresourceKey: context.resourceKey,\n\t\t\t\t\t}, {\n\t\t\t\t\t\ttransportTarget: 'frontend',\n\t\t\t\t\t} );\n\t\t\t\t\tif ( ! result.isValid || ! result.data ) {\n\t\t\t\t\t\tlastBootstrapError =\n\t\t\t\t\t\t\tresult.errors[ 0 ]?.expected ??\n\t\t\t\t\t\t\t'Unable to initialize write access';\n\t\t\t\t\t\tif ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {\n\t\t\t\t\t\t\tawait waitForBootstrapRetry(\n\t\t\t\t\t\t\t\tBOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tclientState.writeExpiry =\n\t\t\t\t\t\tincludePublicWriteCredentials &&\n\t\t\t\t\t\t'publicWriteExpiresAt' in result.data &&\n\t\t\t\t\t\ttypeof result.data.publicWriteExpiresAt === 'number' &&\n\t\t\t\t\t\tresult.data.publicWriteExpiresAt > 0\n\t\t\t\t\t\t\t? result.data.publicWriteExpiresAt\n\t\t\t\t\t\t\t: 0;\n\t\t\t\t\tclientState.writeToken =\n\t\t\t\t\t\tincludePublicWriteCredentials &&\n\t\t\t\t\t\t'publicWriteToken' in result.data &&\n\t\t\t\t\t\ttypeof result.data.publicWriteToken === 'string' &&\n\t\t\t\t\t\tresult.data.publicWriteToken.length > 0\n\t\t\t\t\t\t\t? result.data.publicWriteToken\n\t\t\t\t\t\t\t: '';\n\t\t\t\t\tclientState.writeNonce =\n\t\t\t\t\t\tincludeRestNonce &&\n\t\t\t\t\t\t'restNonce' in result.data &&\n\t\t\t\t\t\ttypeof result.data.restNonce === 'string' &&\n\t\t\t\t\t\tresult.data.restNonce.length > 0\n\t\t\t\t\t\t\t? result.data.restNonce\n\t\t\t\t\t\t\t: '';\n\t\t\t\t\tcontext.bootstrapReady = true;\n\t\t\t\t\tcontext.canWrite =\n\t\t\t\t\t\tresult.data.canWrite === true &&\n\t\t\t\t\t\t(\n\t\t\t\t\t\t\tcontext.persistencePolicy === 'authenticated'\n\t\t\t\t\t\t\t\t? clientState.writeNonce.length > 0\n\t\t\t\t\t\t\t\t: clientState.writeToken.length > 0 &&\n\t\t\t\t\t\t\t\t\t! hasExpiredPublicWriteToken( clientState.writeExpiry )\n\t\t\t\t\t\t);\n\t\t\t\t\tclearBootstrapError( context, clientState );\n\t\t\t\t\tbootstrapSucceeded = true;\n\t\t\t\t\tbreak;\n\t\t\t\t} catch ( error ) {\n\t\t\t\t\tlastBootstrapError =\n\t\t\t\t\t\terror instanceof Error ? error.message : 'Unknown bootstrap error';\n\t\t\t\t\tif ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {\n\t\t\t\t\t\tawait waitForBootstrapRetry(\n\t\t\t\t\t\t\tBOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750\n\t\t\t\t\t\t);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( ! bootstrapSucceeded ) {\n\t\t\t\tcontext.bootstrapReady = false;\n\t\t\t\tcontext.canWrite = false;\n\t\t\t\tclientState.writeExpiry = 0;\n\t\t\t\tclientState.writeNonce = '';\n\t\t\t\tclientState.writeToken = '';\n\t\t\t\tsetBootstrapError( context, clientState, lastBootstrapError );\n\t\t\t}\n\t\t\tcontext.isBootstrapping = false;\n\t\t},\n\t\tasync increment() {\n\t\t\tconst context = getContext< {{pascalCase}}Context >();\n\t\t\tconst clientState = getClientState( context );\n\t\t\tif ( context.postId <= 0 || ! context.resourceKey ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( ! context.bootstrapReady ) {\n\t\t\t\tawait actions.loadBootstrap();\n\t\t\t}\n\t\t\tif ( ! context.bootstrapReady ) {\n\t\t\t\tcontext.error = 'Write access is still initializing.';\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tcontext.persistencePolicy === 'public' &&\n\t\t\t\thasExpiredPublicWriteToken( clientState.writeExpiry )\n\t\t\t) {\n\t\t\t\tawait actions.loadBootstrap();\n\t\t\t}\n\t\t\tif (\n\t\t\t\tcontext.persistencePolicy === 'public' &&\n\t\t\t\thasExpiredPublicWriteToken( clientState.writeExpiry )\n\t\t\t) {\n\t\t\t\tcontext.canWrite = false;\n\t\t\t\tcontext.error = getWriteBlockedMessage( context );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( ! context.canWrite ) {\n\t\t\t\tcontext.error = getWriteBlockedMessage( context );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcontext.isSaving = true;\n\t\t\tcontext.error = '';\n\n\t\t\ttry {\n\t\t\t\tconst result = await writeState( {\n\t\t\t\t\tdelta: 1,\n\t\t\t\t\tpostId: context.postId,\n\t\t\t\t\tpublicWriteRequestId:\n\t\t\t\t\t\tcontext.persistencePolicy === 'public'\n\t\t\t\t\t\t\t? generatePublicWriteRequestId()\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\tpublicWriteToken:\n\t\t\t\t\t\tcontext.persistencePolicy === 'public' &&\n\t\t\t\t\t\tclientState.writeToken.length > 0\n\t\t\t\t\t\t\t? clientState.writeToken\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\tresourceKey: context.resourceKey,\n\t\t\t\t}, {\n\t\t\t\t\trestNonce:\n\t\t\t\t\t\tclientState.writeNonce.length > 0\n\t\t\t\t\t\t\t? clientState.writeNonce\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\ttransportTarget: 'frontend',\n\t\t\t\t} );\n\t\t\t\tif ( ! result.isValid || ! result.data ) {\n\t\t\t\t\tcontext.error = result.errors[ 0 ]?.expected ?? 'Unable to update counter';\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontext.count = result.data.count;\n\t\t\t\tcontext.storage = result.data.storage;\n\t\t\t} catch ( error ) {\n\t\t\t\tcontext.error =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown update error';\n\t\t\t} finally {\n\t\t\t\tcontext.isSaving = false;\n\t\t\t}\n\t\t},\n\t},\n\n\tcallbacks: {\n\t\tinit() {\n\t\t\tconst context = getContext< {{pascalCase}}Context >();\n\t\t\tcontext.client = {\n\t\t\t\tbootstrapError: '',\n\t\t\t\twriteExpiry: 0,\n\t\t\t\twriteNonce: '',\n\t\t\t\twriteToken: '',\n\t\t\t};\n\t\t\tcontext.bootstrapReady = false;\n\t\t\tcontext.canWrite = false;\n\t\t\tcontext.count = 0;\n\t\t\tcontext.error = '';\n\t\t\tcontext.isBootstrapping = false;\n\t\t\tcontext.isLoading = false;\n\t\t\tcontext.isSaving = false;\n\t\t},\n\t\tmounted() {\n\t\t\tstate.isHydrated = true;\n\t\t\tif ( typeof document !== 'undefined' ) {\n\t\t\t\tdocument.documentElement.dataset[ '{{slugCamelCase}}Hydrated' ] = 'true';\n\t\t\t}\n\t\t\tvoid Promise.allSettled( [\n\t\t\t\tactions.loadState(),\n\t\t\t\tactions.loadBootstrap(),\n\t\t\t] );\n\t\t},\n\t},\n} );\n";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type AddBlockTemplateId, type RunAddBlockCommandOptions } from "./cli-add-shared.js";
|
|
2
|
+
/**
|
|
3
|
+
* Seeds an empty official workspace migration project before any blocks are added.
|
|
4
|
+
*
|
|
5
|
+
* @param projectDir Absolute path to the workspace root that will own the
|
|
6
|
+
* migration config and generated scaffold files.
|
|
7
|
+
* @param currentMigrationVersion Initial migration label to record as the
|
|
8
|
+
* current version in the seeded workspace.
|
|
9
|
+
*/
|
|
10
|
+
export declare function seedWorkspaceMigrationProject(projectDir: string, currentMigrationVersion: string): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Adds one built-in block slice to an official workspace project.
|
|
13
|
+
*
|
|
14
|
+
* @param options Command options for the built-in workspace block scaffold flow.
|
|
15
|
+
* @param options.blockName Human-entered block name that will be normalized
|
|
16
|
+
* into the generated workspace block slug.
|
|
17
|
+
* @param options.cwd Working directory used to resolve the nearest official
|
|
18
|
+
* workspace. Defaults to `process.cwd()`.
|
|
19
|
+
* @param options.dataStorageMode Optional storage mode for persistence-capable
|
|
20
|
+
* templates.
|
|
21
|
+
* @param options.persistencePolicy Optional persistence policy for
|
|
22
|
+
* persistence-capable templates.
|
|
23
|
+
* @param options.templateId Built-in block family to scaffold. Defaults to
|
|
24
|
+
* `"basic"`.
|
|
25
|
+
* @returns A promise that resolves with the created block slugs, the owning
|
|
26
|
+
* project directory, and the resolved template id after workspace mutation
|
|
27
|
+
* succeeds.
|
|
28
|
+
* @throws {Error} When the template id is unknown, persistence flags are used
|
|
29
|
+
* with unsupported templates, the command runs outside an official workspace,
|
|
30
|
+
* or target block paths already exist.
|
|
31
|
+
*/
|
|
32
|
+
export declare function runAddBlockCommand({ blockName, cwd, dataStorageMode, persistencePolicy, templateId, }: RunAddBlockCommandOptions): Promise<{
|
|
33
|
+
blockSlugs: string[];
|
|
34
|
+
projectDir: string;
|
|
35
|
+
templateId: AddBlockTemplateId;
|
|
36
|
+
}>;
|