@wp-typia/project-tools 0.13.4 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/scaffold-onboarding.js +8 -4
- package/dist/runtime/scaffold.d.ts +1 -0
- package/dist/runtime/scaffold.js +3 -0
- package/dist/runtime/schema-core.d.ts +1 -0
- package/dist/runtime/schema-core.js +188 -8
- package/package.json +1 -1
- package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +16 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api-types.ts.mustache +10 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api.ts.mustache +51 -38
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/data.ts.mustache +76 -33
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/interactivity.ts.mustache +206 -41
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/render.php.mustache +37 -43
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/transport.ts.mustache +254 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/types.ts.mustache +13 -8
- package/templates/_shared/compound/persistence-auth/{{slugKebabCase}}.php.mustache +139 -0
- package/templates/_shared/compound/persistence-public/{{slugKebabCase}}.php.mustache +159 -0
- package/templates/_shared/persistence/auth/{{slugKebabCase}}.php.mustache +139 -0
- package/templates/_shared/persistence/core/scripts/sync-rest-contracts.ts.mustache +16 -0
- package/templates/_shared/persistence/core/src/api-types.ts.mustache +10 -0
- package/templates/_shared/persistence/core/src/api.ts.mustache +51 -38
- package/templates/_shared/persistence/core/src/data.ts.mustache +76 -33
- package/templates/_shared/persistence/core/src/interactivity.ts.mustache +206 -43
- package/templates/_shared/persistence/core/src/transport.ts.mustache +254 -0
- package/templates/_shared/persistence/public/{{slugKebabCase}}.php.mustache +159 -0
- package/templates/_shared/rest-helpers/public/inc/rest-public.php.mustache +1 -1
- package/templates/_shared/workspace/persistence-auth/server.php.mustache +139 -0
- package/templates/_shared/workspace/persistence-public/inc/rest-public.php.mustache +1 -1
- package/templates/_shared/workspace/persistence-public/server.php.mustache +159 -0
- package/templates/persistence/src/edit.tsx.mustache +1 -1
- package/templates/persistence/src/render.php.mustache +37 -43
- package/templates/persistence/src/types.ts.mustache +13 -9
package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/data.ts.mustache
CHANGED
|
@@ -6,31 +6,21 @@ import {
|
|
|
6
6
|
} from '@wp-typia/rest/react';
|
|
7
7
|
|
|
8
8
|
import type {
|
|
9
|
+
{{pascalCase}}BootstrapQuery,
|
|
10
|
+
{{pascalCase}}BootstrapResponse,
|
|
9
11
|
{{pascalCase}}StateQuery,
|
|
10
12
|
{{pascalCase}}StateResponse,
|
|
11
13
|
{{pascalCase}}WriteStateRequest,
|
|
12
14
|
} from './api-types';
|
|
13
15
|
import {
|
|
14
|
-
|
|
16
|
+
bootstrapEndpoint,
|
|
15
17
|
stateEndpoint,
|
|
16
18
|
writeStateEndpoint,
|
|
17
19
|
} from './api';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return nonce
|
|
24
|
-
? {
|
|
25
|
-
requestOptions: {
|
|
26
|
-
headers: {
|
|
27
|
-
'X-WP-Nonce': nonce,
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
}
|
|
31
|
-
: undefined;
|
|
32
|
-
}
|
|
33
|
-
{{/isAuthenticatedPersistencePolicy}}
|
|
20
|
+
import {
|
|
21
|
+
resolveTransportCallOptions,
|
|
22
|
+
type PersistenceTransportTarget,
|
|
23
|
+
} from './transport';
|
|
34
24
|
|
|
35
25
|
interface WriteStateMutationContext< Context > {
|
|
36
26
|
previous:
|
|
@@ -49,9 +39,22 @@ export interface Use{{pascalCase}}StateQueryOptions<
|
|
|
49
39
|
>,
|
|
50
40
|
'resolveCallOptions'
|
|
51
41
|
> {
|
|
52
|
-
{{#isAuthenticatedPersistencePolicy}}
|
|
53
42
|
restNonce?: string;
|
|
54
|
-
|
|
43
|
+
transportTarget?: PersistenceTransportTarget;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface Use{{pascalCase}}BootstrapQueryOptions<
|
|
47
|
+
Selected = {{pascalCase}}BootstrapResponse,
|
|
48
|
+
> extends Omit<
|
|
49
|
+
UseEndpointQueryOptions<
|
|
50
|
+
{{pascalCase}}BootstrapQuery,
|
|
51
|
+
{{pascalCase}}BootstrapResponse,
|
|
52
|
+
Selected
|
|
53
|
+
>,
|
|
54
|
+
'resolveCallOptions'
|
|
55
|
+
> {
|
|
56
|
+
restNonce?: string;
|
|
57
|
+
transportTarget?: PersistenceTransportTarget;
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
export interface UseWrite{{pascalCase}}StateMutationOptions<
|
|
@@ -62,7 +65,7 @@ export interface UseWrite{{pascalCase}}StateMutationOptions<
|
|
|
62
65
|
{{pascalCase}}StateResponse,
|
|
63
66
|
WriteStateMutationContext< Context >
|
|
64
67
|
>,
|
|
65
|
-
'invalidate' | 'onError' | 'onMutate'
|
|
68
|
+
'invalidate' | 'onError' | 'onMutate' | 'resolveCallOptions'
|
|
66
69
|
> {
|
|
67
70
|
onError?: (
|
|
68
71
|
error: unknown,
|
|
@@ -74,9 +77,8 @@ export interface UseWrite{{pascalCase}}StateMutationOptions<
|
|
|
74
77
|
request: {{pascalCase}}WriteStateRequest,
|
|
75
78
|
client: import('@wp-typia/rest/react').EndpointDataClient
|
|
76
79
|
) => Context | Promise<Context>;
|
|
77
|
-
{{#isAuthenticatedPersistencePolicy}}
|
|
78
80
|
restNonce?: string;
|
|
79
|
-
|
|
81
|
+
transportTarget?: PersistenceTransportTarget;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
export function use{{pascalCase}}StateQuery<
|
|
@@ -85,20 +87,54 @@ export function use{{pascalCase}}StateQuery<
|
|
|
85
87
|
request: {{pascalCase}}StateQuery,
|
|
86
88
|
options: Use{{pascalCase}}StateQueryOptions< Selected > = {}
|
|
87
89
|
) {
|
|
88
|
-
{{#isAuthenticatedPersistencePolicy}}
|
|
89
90
|
const {
|
|
90
91
|
restNonce,
|
|
92
|
+
transportTarget = 'editor',
|
|
91
93
|
...queryOptions
|
|
92
94
|
} = options;
|
|
93
95
|
|
|
94
96
|
return useEndpointQuery( stateEndpoint, request, {
|
|
95
97
|
...queryOptions,
|
|
96
|
-
resolveCallOptions: () =>
|
|
98
|
+
resolveCallOptions: () =>
|
|
99
|
+
resolveTransportCallOptions(
|
|
100
|
+
transportTarget,
|
|
101
|
+
'read',
|
|
102
|
+
stateEndpoint,
|
|
103
|
+
request,
|
|
104
|
+
{
|
|
105
|
+
restNonce,
|
|
106
|
+
transportTarget,
|
|
107
|
+
}
|
|
108
|
+
),
|
|
109
|
+
} );
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function use{{pascalCase}}BootstrapQuery<
|
|
113
|
+
Selected = {{pascalCase}}BootstrapResponse,
|
|
114
|
+
>(
|
|
115
|
+
request: {{pascalCase}}BootstrapQuery,
|
|
116
|
+
options: Use{{pascalCase}}BootstrapQueryOptions< Selected > = {}
|
|
117
|
+
) {
|
|
118
|
+
const {
|
|
119
|
+
restNonce,
|
|
120
|
+
transportTarget = 'editor',
|
|
121
|
+
...queryOptions
|
|
122
|
+
} = options;
|
|
123
|
+
|
|
124
|
+
return useEndpointQuery( bootstrapEndpoint, request, {
|
|
125
|
+
...queryOptions,
|
|
126
|
+
resolveCallOptions: () =>
|
|
127
|
+
resolveTransportCallOptions(
|
|
128
|
+
transportTarget,
|
|
129
|
+
'read',
|
|
130
|
+
bootstrapEndpoint,
|
|
131
|
+
request,
|
|
132
|
+
{
|
|
133
|
+
restNonce,
|
|
134
|
+
transportTarget,
|
|
135
|
+
}
|
|
136
|
+
),
|
|
97
137
|
} );
|
|
98
|
-
{{/isAuthenticatedPersistencePolicy}}
|
|
99
|
-
{{^isAuthenticatedPersistencePolicy}}
|
|
100
|
-
return useEndpointQuery( stateEndpoint, request, options );
|
|
101
|
-
{{/isAuthenticatedPersistencePolicy}}
|
|
102
138
|
}
|
|
103
139
|
|
|
104
140
|
export function useWrite{{pascalCase}}StateMutation<
|
|
@@ -109,9 +145,8 @@ export function useWrite{{pascalCase}}StateMutation<
|
|
|
109
145
|
const {
|
|
110
146
|
onError,
|
|
111
147
|
onMutate,
|
|
112
|
-
{{#isAuthenticatedPersistencePolicy}}
|
|
113
148
|
restNonce,
|
|
114
|
-
|
|
149
|
+
transportTarget = 'editor',
|
|
115
150
|
...mutationOptions
|
|
116
151
|
} = options;
|
|
117
152
|
|
|
@@ -185,8 +220,16 @@ export function useWrite{{pascalCase}}StateMutation<
|
|
|
185
220
|
userContext,
|
|
186
221
|
};
|
|
187
222
|
},
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
223
|
+
resolveCallOptions: ( request ) =>
|
|
224
|
+
resolveTransportCallOptions(
|
|
225
|
+
transportTarget,
|
|
226
|
+
'write',
|
|
227
|
+
writeStateEndpoint,
|
|
228
|
+
request,
|
|
229
|
+
{
|
|
230
|
+
restNonce,
|
|
231
|
+
transportTarget,
|
|
232
|
+
}
|
|
233
|
+
),
|
|
191
234
|
} );
|
|
192
235
|
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { getContext, store } from '@wordpress/interactivity';
|
|
2
2
|
import { generatePublicWriteRequestId } from '@wp-typia/block-runtime/identifiers';
|
|
3
3
|
|
|
4
|
-
import { fetchState, writeState } from './api';
|
|
5
|
-
import type {
|
|
4
|
+
import { fetchBootstrap, fetchState, writeState } from './api';
|
|
5
|
+
import type {
|
|
6
|
+
{{pascalCase}}ClientState,
|
|
7
|
+
{{pascalCase}}Context,
|
|
8
|
+
{{pascalCase}}State,
|
|
9
|
+
} from './types';
|
|
6
10
|
|
|
7
11
|
function hasExpiredPublicWriteToken(
|
|
8
|
-
|
|
12
|
+
expiresAt?: number
|
|
9
13
|
): boolean {
|
|
10
14
|
return (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Date.now() >= context.publicWriteExpiresAt * 1000
|
|
15
|
+
typeof expiresAt === 'number' &&
|
|
16
|
+
expiresAt > 0 &&
|
|
17
|
+
Date.now() >= expiresAt * 1000
|
|
15
18
|
);
|
|
16
19
|
}
|
|
17
20
|
|
|
@@ -20,66 +23,212 @@ function getWriteBlockedMessage(
|
|
|
20
23
|
): string {
|
|
21
24
|
return context.persistencePolicy === 'authenticated'
|
|
22
25
|
? 'Sign in to persist this counter.'
|
|
23
|
-
: '
|
|
26
|
+
: 'Public writes are temporarily unavailable.';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const BOOTSTRAP_MAX_ATTEMPTS = 3;
|
|
30
|
+
const BOOTSTRAP_RETRY_DELAYS_MS = [ 250, 500 ];
|
|
31
|
+
|
|
32
|
+
async function waitForBootstrapRetry( delayMs: number ): Promise< void > {
|
|
33
|
+
await new Promise( ( resolve ) => {
|
|
34
|
+
setTimeout( resolve, delayMs );
|
|
35
|
+
} );
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getClientState(
|
|
39
|
+
context: {{pascalCase}}Context
|
|
40
|
+
): {{pascalCase}}ClientState {
|
|
41
|
+
if ( context.client ) {
|
|
42
|
+
return context.client;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
context.client = {
|
|
46
|
+
bootstrapError: '',
|
|
47
|
+
writeExpiry: 0,
|
|
48
|
+
writeNonce: '',
|
|
49
|
+
writeToken: '',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return context.client;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function clearBootstrapError(
|
|
56
|
+
context: {{pascalCase}}Context,
|
|
57
|
+
clientState: {{pascalCase}}ClientState
|
|
58
|
+
): void {
|
|
59
|
+
if ( context.error === clientState.bootstrapError ) {
|
|
60
|
+
context.error = '';
|
|
61
|
+
}
|
|
62
|
+
clientState.bootstrapError = '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function setBootstrapError(
|
|
66
|
+
context: {{pascalCase}}Context,
|
|
67
|
+
clientState: {{pascalCase}}ClientState,
|
|
68
|
+
message: string
|
|
69
|
+
): void {
|
|
70
|
+
clientState.bootstrapError = message;
|
|
71
|
+
context.error = message;
|
|
24
72
|
}
|
|
25
73
|
|
|
26
74
|
const { actions, state } = store( '{{slugKebabCase}}', {
|
|
27
75
|
state: {
|
|
28
|
-
canWrite: false,
|
|
29
|
-
count: 0,
|
|
30
|
-
error: undefined,
|
|
31
76
|
isHydrated: false,
|
|
32
|
-
isLoading: false,
|
|
33
|
-
isSaving: false,
|
|
34
77
|
} as {{pascalCase}}State,
|
|
35
78
|
|
|
36
79
|
actions: {
|
|
37
|
-
async
|
|
80
|
+
async loadState() {
|
|
38
81
|
const context = getContext< {{pascalCase}}Context >();
|
|
39
82
|
if ( context.postId <= 0 || ! context.resourceKey ) {
|
|
40
83
|
return;
|
|
41
84
|
}
|
|
42
85
|
|
|
43
|
-
|
|
44
|
-
|
|
86
|
+
context.isLoading = true;
|
|
87
|
+
context.error = '';
|
|
45
88
|
|
|
46
89
|
try {
|
|
47
90
|
const result = await fetchState( {
|
|
48
91
|
postId: context.postId,
|
|
49
92
|
resourceKey: context.resourceKey,
|
|
93
|
+
}, {
|
|
94
|
+
transportTarget: 'frontend',
|
|
50
95
|
} );
|
|
51
96
|
if ( ! result.isValid || ! result.data ) {
|
|
52
|
-
|
|
97
|
+
context.error = result.errors[ 0 ]?.expected ?? 'Unable to load counter';
|
|
53
98
|
return;
|
|
54
99
|
}
|
|
55
100
|
context.count = result.data.count;
|
|
56
|
-
context.storage = result.data.storage;
|
|
57
|
-
state.count = result.data.count;
|
|
58
101
|
} catch ( error ) {
|
|
59
|
-
|
|
102
|
+
context.error =
|
|
60
103
|
error instanceof Error ? error.message : 'Unknown loading error';
|
|
61
104
|
} finally {
|
|
62
|
-
|
|
105
|
+
context.isLoading = false;
|
|
63
106
|
}
|
|
64
107
|
},
|
|
108
|
+
async loadBootstrap() {
|
|
109
|
+
const context = getContext< {{pascalCase}}Context >();
|
|
110
|
+
const clientState = getClientState( context );
|
|
111
|
+
if ( context.postId <= 0 || ! context.resourceKey ) {
|
|
112
|
+
context.bootstrapReady = true;
|
|
113
|
+
context.canWrite = false;
|
|
114
|
+
clientState.bootstrapError = '';
|
|
115
|
+
clientState.writeExpiry = 0;
|
|
116
|
+
clientState.writeNonce = '';
|
|
117
|
+
clientState.writeToken = '';
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
context.isBootstrapping = true;
|
|
122
|
+
|
|
123
|
+
let bootstrapSucceeded = false;
|
|
124
|
+
let lastBootstrapError =
|
|
125
|
+
'Unable to initialize write access';
|
|
126
|
+
|
|
127
|
+
for ( let attempt = 1; attempt <= BOOTSTRAP_MAX_ATTEMPTS; attempt += 1 ) {
|
|
128
|
+
try {
|
|
129
|
+
const result = await fetchBootstrap( {
|
|
130
|
+
postId: context.postId,
|
|
131
|
+
resourceKey: context.resourceKey,
|
|
132
|
+
}, {
|
|
133
|
+
transportTarget: 'frontend',
|
|
134
|
+
} );
|
|
135
|
+
if ( ! result.isValid || ! result.data ) {
|
|
136
|
+
lastBootstrapError =
|
|
137
|
+
result.errors[ 0 ]?.expected ??
|
|
138
|
+
'Unable to initialize write access';
|
|
139
|
+
if ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {
|
|
140
|
+
await waitForBootstrapRetry(
|
|
141
|
+
BOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750
|
|
142
|
+
);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
clientState.writeExpiry =
|
|
149
|
+
typeof result.data.publicWriteExpiresAt === 'number' &&
|
|
150
|
+
result.data.publicWriteExpiresAt > 0
|
|
151
|
+
? result.data.publicWriteExpiresAt
|
|
152
|
+
: 0;
|
|
153
|
+
clientState.writeToken =
|
|
154
|
+
typeof result.data.publicWriteToken === 'string' &&
|
|
155
|
+
result.data.publicWriteToken.length > 0
|
|
156
|
+
? result.data.publicWriteToken
|
|
157
|
+
: '';
|
|
158
|
+
clientState.writeNonce =
|
|
159
|
+
typeof result.data.restNonce === 'string' &&
|
|
160
|
+
result.data.restNonce.length > 0
|
|
161
|
+
? result.data.restNonce
|
|
162
|
+
: '';
|
|
163
|
+
context.bootstrapReady = true;
|
|
164
|
+
context.canWrite =
|
|
165
|
+
result.data.canWrite === true &&
|
|
166
|
+
(
|
|
167
|
+
context.persistencePolicy === 'authenticated'
|
|
168
|
+
? clientState.writeNonce.length > 0
|
|
169
|
+
: clientState.writeToken.length > 0 &&
|
|
170
|
+
! hasExpiredPublicWriteToken( clientState.writeExpiry )
|
|
171
|
+
);
|
|
172
|
+
clearBootstrapError( context, clientState );
|
|
173
|
+
bootstrapSucceeded = true;
|
|
174
|
+
break;
|
|
175
|
+
} catch ( error ) {
|
|
176
|
+
lastBootstrapError =
|
|
177
|
+
error instanceof Error ? error.message : 'Unknown bootstrap error';
|
|
178
|
+
if ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {
|
|
179
|
+
await waitForBootstrapRetry(
|
|
180
|
+
BOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750
|
|
181
|
+
);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if ( ! bootstrapSucceeded ) {
|
|
189
|
+
context.bootstrapReady = false;
|
|
190
|
+
context.canWrite = false;
|
|
191
|
+
clientState.writeExpiry = 0;
|
|
192
|
+
clientState.writeNonce = '';
|
|
193
|
+
clientState.writeToken = '';
|
|
194
|
+
setBootstrapError( context, clientState, lastBootstrapError );
|
|
195
|
+
}
|
|
196
|
+
context.isBootstrapping = false;
|
|
197
|
+
},
|
|
65
198
|
async increment() {
|
|
66
199
|
const context = getContext< {{pascalCase}}Context >();
|
|
200
|
+
const clientState = getClientState( context );
|
|
67
201
|
if ( context.postId <= 0 || ! context.resourceKey ) {
|
|
68
202
|
return;
|
|
69
203
|
}
|
|
70
|
-
if (
|
|
204
|
+
if ( ! context.bootstrapReady ) {
|
|
205
|
+
await actions.loadBootstrap();
|
|
206
|
+
}
|
|
207
|
+
if ( ! context.bootstrapReady ) {
|
|
208
|
+
context.error = 'Write access is still initializing.';
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (
|
|
212
|
+
context.persistencePolicy === 'public' &&
|
|
213
|
+
hasExpiredPublicWriteToken( clientState.writeExpiry )
|
|
214
|
+
) {
|
|
215
|
+
await actions.loadBootstrap();
|
|
216
|
+
}
|
|
217
|
+
if (
|
|
218
|
+
context.persistencePolicy === 'public' &&
|
|
219
|
+
hasExpiredPublicWriteToken( clientState.writeExpiry )
|
|
220
|
+
) {
|
|
71
221
|
context.canWrite = false;
|
|
72
|
-
|
|
73
|
-
state.error = getWriteBlockedMessage( context );
|
|
222
|
+
context.error = getWriteBlockedMessage( context );
|
|
74
223
|
return;
|
|
75
224
|
}
|
|
76
|
-
if ( ! context.canWrite
|
|
77
|
-
|
|
225
|
+
if ( ! context.canWrite ) {
|
|
226
|
+
context.error = getWriteBlockedMessage( context );
|
|
78
227
|
return;
|
|
79
228
|
}
|
|
80
229
|
|
|
81
|
-
|
|
82
|
-
|
|
230
|
+
context.isSaving = true;
|
|
231
|
+
context.error = '';
|
|
83
232
|
|
|
84
233
|
try {
|
|
85
234
|
const result = await writeState( {
|
|
@@ -91,24 +240,28 @@ const { actions, state } = store( '{{slugKebabCase}}', {
|
|
|
91
240
|
: undefined,
|
|
92
241
|
publicWriteToken:
|
|
93
242
|
context.persistencePolicy === 'public' &&
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
? context.publicWriteToken
|
|
243
|
+
clientState.writeToken.length > 0
|
|
244
|
+
? clientState.writeToken
|
|
97
245
|
: undefined,
|
|
98
246
|
resourceKey: context.resourceKey,
|
|
99
|
-
},
|
|
247
|
+
}, {
|
|
248
|
+
restNonce:
|
|
249
|
+
clientState.writeNonce.length > 0
|
|
250
|
+
? clientState.writeNonce
|
|
251
|
+
: undefined,
|
|
252
|
+
transportTarget: 'frontend',
|
|
253
|
+
} );
|
|
100
254
|
if ( ! result.isValid || ! result.data ) {
|
|
101
|
-
|
|
255
|
+
context.error = result.errors[ 0 ]?.expected ?? 'Unable to update counter';
|
|
102
256
|
return;
|
|
103
257
|
}
|
|
104
258
|
context.count = result.data.count;
|
|
105
259
|
context.storage = result.data.storage;
|
|
106
|
-
state.count = result.data.count;
|
|
107
260
|
} catch ( error ) {
|
|
108
|
-
|
|
261
|
+
context.error =
|
|
109
262
|
error instanceof Error ? error.message : 'Unknown update error';
|
|
110
263
|
} finally {
|
|
111
|
-
|
|
264
|
+
context.isSaving = false;
|
|
112
265
|
}
|
|
113
266
|
},
|
|
114
267
|
},
|
|
@@ -116,17 +269,29 @@ const { actions, state } = store( '{{slugKebabCase}}', {
|
|
|
116
269
|
callbacks: {
|
|
117
270
|
init() {
|
|
118
271
|
const context = getContext< {{pascalCase}}Context >();
|
|
119
|
-
context.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
272
|
+
context.client = {
|
|
273
|
+
bootstrapError: '',
|
|
274
|
+
writeExpiry: 0,
|
|
275
|
+
writeNonce: '',
|
|
276
|
+
writeToken: '',
|
|
277
|
+
};
|
|
278
|
+
context.bootstrapReady = false;
|
|
279
|
+
context.canWrite = false;
|
|
280
|
+
context.count = 0;
|
|
281
|
+
context.error = '';
|
|
282
|
+
context.isBootstrapping = false;
|
|
283
|
+
context.isLoading = false;
|
|
284
|
+
context.isSaving = false;
|
|
123
285
|
},
|
|
124
286
|
mounted() {
|
|
125
287
|
state.isHydrated = true;
|
|
126
288
|
if ( typeof document !== 'undefined' ) {
|
|
127
289
|
document.documentElement.dataset[ '{{slugCamelCase}}Hydrated' ] = 'true';
|
|
128
290
|
}
|
|
129
|
-
void
|
|
291
|
+
void Promise.allSettled( [
|
|
292
|
+
actions.loadState(),
|
|
293
|
+
actions.loadBootstrap(),
|
|
294
|
+
] );
|
|
130
295
|
},
|
|
131
296
|
},
|
|
132
297
|
} );
|
package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/render.php.mustache
CHANGED
|
@@ -32,50 +32,42 @@ $post_id = is_object( $block ) && isset( $block->context['postId'] )
|
|
|
32
32
|
: (int) get_queried_object_id();
|
|
33
33
|
$storage_mode = '{{dataStorageMode}}';
|
|
34
34
|
$persistence_policy = '{{persistencePolicy}}';
|
|
35
|
-
|
|
35
|
+
|
|
36
|
+
{{phpPrefix}}_record_rendered_block_instance(
|
|
37
|
+
(int) $post_id,
|
|
38
|
+
'{{namespace}}/{{slugKebabCase}}',
|
|
39
|
+
$resource_key
|
|
40
|
+
);
|
|
41
|
+
|
|
36
42
|
$notice_message = 'authenticated' === $persistence_policy
|
|
37
43
|
? __( 'Sign in to persist this counter.', '{{textDomain}}' )
|
|
38
|
-
: __( '
|
|
44
|
+
: __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
|
|
39
45
|
|
|
40
46
|
if ( empty( $validation['valid'] ) || '' === $resource_key ) {
|
|
41
47
|
return '';
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
$context = array(
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
'bootstrapReady' => false,
|
|
52
|
+
'buttonLabel' => $button_label,
|
|
53
|
+
'canWrite' => false,
|
|
54
|
+
'client' => array(
|
|
55
|
+
'writeExpiry' => 0,
|
|
56
|
+
'writeNonce' => '',
|
|
57
|
+
'writeToken' => '',
|
|
58
|
+
),
|
|
59
|
+
'count' => 0,
|
|
60
|
+
'error' => '',
|
|
61
|
+
'isBootstrapping' => false,
|
|
62
|
+
'isLoading' => false,
|
|
63
|
+
'isSaving' => false,
|
|
64
|
+
'persistencePolicy' => $persistence_policy,
|
|
65
|
+
'postId' => (int) $post_id,
|
|
66
|
+
'resourceKey' => $resource_key,
|
|
67
|
+
'showCount' => $show_count,
|
|
68
|
+
'storage' => $storage_mode,
|
|
53
69
|
);
|
|
54
70
|
|
|
55
|
-
if ( 'authenticated' === $persistence_policy ) {
|
|
56
|
-
$can_write = $post_id > 0 && is_user_logged_in();
|
|
57
|
-
if ( $can_write ) {
|
|
58
|
-
$context['restNonce'] = wp_create_nonce( 'wp_rest' );
|
|
59
|
-
}
|
|
60
|
-
} elseif ( $post_id > 0 && function_exists( '{{phpPrefix}}_create_public_write_token' ) ) {
|
|
61
|
-
$public_write = {{phpPrefix}}_create_public_write_token( (int) $post_id, $resource_key );
|
|
62
|
-
if ( is_array( $public_write ) ) {
|
|
63
|
-
$token = isset( $public_write['token'] ) ? (string) $public_write['token'] : '';
|
|
64
|
-
$expires_at = isset( $public_write['expiresAt'] ) ? (int) $public_write['expiresAt'] : 0;
|
|
65
|
-
|
|
66
|
-
if ( '' !== $token ) {
|
|
67
|
-
$context['publicWriteToken'] = $token;
|
|
68
|
-
$can_write = true;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if ( $expires_at > 0 ) {
|
|
72
|
-
$context['publicWriteExpiresAt'] = $expires_at;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
$context['canWrite'] = $can_write;
|
|
78
|
-
|
|
79
71
|
$allowed_inner_html = wp_kses_allowed_html( 'post' );
|
|
80
72
|
|
|
81
73
|
foreach ( $allowed_inner_html as &$allowed_attributes ) {
|
|
@@ -119,18 +111,20 @@ $wrapper_attributes = get_block_wrapper_attributes(
|
|
|
119
111
|
<?php if ( '' !== $intro ) : ?>
|
|
120
112
|
<p class="{{cssClassName}}__intro"><?php echo esc_html( $intro ); ?></p>
|
|
121
113
|
<?php endif; ?>
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
114
|
+
<p
|
|
115
|
+
class="{{cssClassName}}__notice"
|
|
116
|
+
data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
|
|
117
|
+
hidden
|
|
118
|
+
>
|
|
119
|
+
<?php echo esc_html( $notice_message ); ?>
|
|
120
|
+
</p>
|
|
127
121
|
<p
|
|
128
122
|
class="{{cssClassName}}__error"
|
|
129
123
|
role="status"
|
|
130
124
|
aria-live="polite"
|
|
131
125
|
aria-atomic="true"
|
|
132
|
-
data-wp-bind--hidden="!
|
|
133
|
-
data-wp-text="
|
|
126
|
+
data-wp-bind--hidden="!context.error"
|
|
127
|
+
data-wp-text="context.error"
|
|
134
128
|
hidden
|
|
135
129
|
></p>
|
|
136
130
|
<?php if ( $show_count ) : ?>
|
|
@@ -140,11 +134,11 @@ $wrapper_attributes = get_block_wrapper_attributes(
|
|
|
140
134
|
role="status"
|
|
141
135
|
aria-live="polite"
|
|
142
136
|
aria-atomic="true"
|
|
143
|
-
data-wp-text="
|
|
137
|
+
data-wp-text="context.count"
|
|
144
138
|
>0</span>
|
|
145
139
|
<button
|
|
146
140
|
type="button"
|
|
147
|
-
|
|
141
|
+
disabled
|
|
148
142
|
data-wp-bind--disabled="!context.canWrite"
|
|
149
143
|
data-wp-on--click="actions.increment"
|
|
150
144
|
>
|