base-js-sw 1.0.11 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -106,6 +106,39 @@ const getResultStatus = (result) => {
106
106
  ).toLowerCase();
107
107
  };
108
108
 
109
+ const getPostId = (value) => {
110
+ if (typeof value === 'number') {
111
+ return Number.isInteger(value) && value > 0 ? value : '';
112
+ }
113
+
114
+ if (typeof value !== 'string') {
115
+ return '';
116
+ }
117
+
118
+ const trimmedValue = value.trim();
119
+
120
+ if (!/^\d+$/.test(trimmedValue)) {
121
+ return '';
122
+ }
123
+
124
+ const numericValue = Number(trimmedValue);
125
+
126
+ return Number.isSafeInteger(numericValue) && numericValue > 0
127
+ ? numericValue
128
+ : '';
129
+ };
130
+
131
+ const isDirectUrlValue = ({ id, kind, type, url }) => {
132
+ const normalizedKind = String(kind || '').toLowerCase();
133
+ const normalizedType = String(type || '').toLowerCase();
134
+
135
+ return (
136
+ normalizedKind === 'url' ||
137
+ normalizedType === 'url' ||
138
+ (typeof id === 'string' && id === url)
139
+ );
140
+ };
141
+
109
142
  const getLinkedPostStatusMessage = (result) => {
110
143
  const status = getResultStatus(result);
111
144
 
@@ -151,84 +184,105 @@ const BrokenLinkBadge = ({ message }) => {
151
184
  );
152
185
  };
153
186
 
154
- export const LinkPicker = ({ setAttributes, at, linkRef = 'link' }) => {
187
+ export const LinkPicker = ({
188
+ setAttributes,
189
+ at,
190
+ linkRef = 'link'
191
+ }) => {
192
+
155
193
  const linkObj = at[linkRef] || {};
156
- const { url, linkTarget, id } = linkObj;
157
- const opensInNewTab = linkTarget === '_blank';
158
194
 
159
- const [localUrl, setLocalUrl] = useState(url || '');
160
- const [localId, setLocalId] = useState(id || '');
161
- const [localLinkTarget, setLocalLinkTarget] = useState(opensInNewTab);
195
+ const {
196
+ url,
197
+ linkTarget,
198
+ id
199
+ } = linkObj;
162
200
 
163
- const [linkedPostStatus, setLinkedPostStatus] = useState({
164
- isLoading: false,
165
- result: null,
166
- });
201
+ const opensInNewTab =
202
+ linkTarget === '_blank';
167
203
 
168
- const isInternalPost = Boolean(localId);
169
- const hasLinkData = Boolean(localUrl || localId);
204
+ const initialPostId =
205
+ getPostId(id);
170
206
 
171
- const statusLabel = isInternalPost
172
- ? 'Dynamic WordPress link'
173
- : 'External link';
207
+ const [localUrl, setLocalUrl] =
208
+ useState(url || '');
174
209
 
175
- const currentStatus = linkedPostStatus.result
176
- ? getResultStatus(linkedPostStatus.result)
177
- : '';
210
+ const [localId, setLocalId] =
211
+ useState(initialPostId);
178
212
 
179
- const brokenLinkMessage = linkedPostStatus.result
180
- ? getLinkedPostStatusMessage(linkedPostStatus.result)
181
- : '';
213
+ const [localLinkTarget, setLocalLinkTarget] =
214
+ useState(opensInNewTab);
215
+
216
+ const [linkedPostStatus, setLinkedPostStatus] =
217
+ useState({
218
+ isLoading: false,
219
+ result: null,
220
+ });
221
+
222
+ const postId =
223
+ getPostId(localId);
224
+
225
+ const isInternalPost =
226
+ Boolean(postId);
227
+
228
+ const hasLinkData =
229
+ Boolean(localUrl || postId);
230
+
231
+ const statusLabel =
232
+ isInternalPost
233
+ ? 'Dynamic WordPress link'
234
+ : 'External link';
235
+
236
+ const currentStatus =
237
+ linkedPostStatus.result
238
+ ? getResultStatus(linkedPostStatus.result)
239
+ : '';
240
+
241
+ const brokenLinkMessage =
242
+ linkedPostStatus.result
243
+ ? getLinkedPostStatusMessage(linkedPostStatus.result)
244
+ : '';
182
245
 
183
246
  const shouldShowBrokenLinkStatus =
184
- isInternalPost &&
185
- linkedPostStatus.result &&
186
- STATUSES.includes(currentStatus);
247
+ isInternalPost
248
+ && linkedPostStatus.result
249
+ && STATUSES.includes(currentStatus);
187
250
 
188
251
  useEffect(() => {
189
- setAttributes({
190
- [linkRef]: {
191
- url: localUrl
192
- ? encodeURI(safeDecodeURI(localUrl))
193
- : '',
194
- linkTarget: localLinkTarget
195
- ? '_blank'
196
- : undefined,
197
- id: localId
198
- ? localId
199
- : undefined,
200
- },
201
- });
202
- }, [localUrl, localId, localLinkTarget]);
203
252
 
204
- useEffect(() => {
205
- const numericPostId = parseInt(localId, 10);
253
+ const numericPostId =
254
+ getPostId(localId);
206
255
 
207
256
  const hasValidPostId =
208
- Number.isInteger(numericPostId) &&
209
- numericPostId > 0;
257
+ Boolean(numericPostId);
210
258
 
211
259
  if (!hasValidPostId) {
260
+
212
261
  setLinkedPostStatus({
213
262
  isLoading: false,
214
263
  result: null,
215
264
  });
216
265
 
217
266
  return;
267
+
218
268
  }
219
269
 
220
270
  if (linkedPostStatusCache[numericPostId]) {
271
+
221
272
  setLinkedPostStatus({
222
273
  isLoading: false,
223
274
  result: linkedPostStatusCache[numericPostId],
224
275
  });
225
276
 
226
277
  return;
278
+
227
279
  }
228
280
 
229
- const apiFetch = window.wp?.apiFetch;
281
+ const apiFetch =
282
+ window.wp?.apiFetch;
230
283
 
231
284
  if (!apiFetch) {
285
+
232
286
  setLinkedPostStatus({
233
287
  isLoading: false,
234
288
  result: {
@@ -238,6 +292,7 @@ export const LinkPicker = ({ setAttributes, at, linkRef = 'link' }) => {
238
292
  });
239
293
 
240
294
  return;
295
+
241
296
  }
242
297
 
243
298
  let isMounted = true;
@@ -251,18 +306,22 @@ export const LinkPicker = ({ setAttributes, at, linkRef = 'link' }) => {
251
306
  path: `/theme/v1/linked-post-validator/post/${numericPostId}`,
252
307
  })
253
308
  .then((result) => {
309
+
254
310
  if (!isMounted) {
255
311
  return;
256
312
  }
257
313
 
258
- linkedPostStatusCache[numericPostId] = result;
314
+ linkedPostStatusCache[numericPostId] =
315
+ result;
259
316
 
260
317
  setLinkedPostStatus({
261
318
  isLoading: false,
262
319
  result,
263
320
  });
321
+
264
322
  })
265
323
  .catch(() => {
324
+
266
325
  if (!isMounted) {
267
326
  return;
268
327
  }
@@ -276,73 +335,99 @@ export const LinkPicker = ({ setAttributes, at, linkRef = 'link' }) => {
276
335
  isLoading: false,
277
336
  result: errorResult,
278
337
  });
338
+
279
339
  });
280
340
 
281
341
  return () => {
282
342
  isMounted = false;
283
343
  };
344
+
284
345
  }, [localId]);
285
346
 
286
- const handleLinkChange = (updatedValue) => {
287
- const { url, opensInNewTab, id } = updatedValue;
347
+ const handleLinkChange = (updatedValue = {}) => {
288
348
 
289
- const nextUrl = url || '';
290
- const isPostSelection = Boolean(id);
349
+ const {
350
+ url,
351
+ opensInNewTab,
352
+ id,
353
+ kind,
354
+ type,
355
+ } = updatedValue;
291
356
 
292
- if (isPostSelection) {
293
- setLocalId(id);
294
- setLocalUrl(nextUrl);
295
- } else {
296
- setLocalUrl(nextUrl);
297
- setLocalId('');
298
- }
357
+ const nextUrl =
358
+ url || '';
359
+
360
+ /**
361
+ * When manually typing,
362
+ * Gutenberg keeps previous selected id.
363
+ *
364
+ * Manual typing has no kind/type.
365
+ */
366
+ const isManualInput =
367
+ !kind
368
+ && !type;
369
+
370
+ const nextPostId =
371
+ isManualInput
372
+ ? undefined
373
+ : getPostId(id);
374
+
375
+ setLocalUrl(nextUrl);
376
+
377
+ setLocalId(
378
+ nextPostId || ''
379
+ );
380
+
381
+ setLocalLinkTarget(
382
+ Boolean(opensInNewTab)
383
+ );
384
+
385
+ setAttributes({
386
+ [linkRef]: {
387
+ url: nextUrl
388
+ ? encodeURI(safeDecodeURI(nextUrl))
389
+ : '',
390
+ linkTarget: opensInNewTab
391
+ ? '_blank'
392
+ : undefined,
393
+ id: nextPostId || undefined,
394
+ },
395
+ });
299
396
 
300
- setLocalLinkTarget(Boolean(opensInNewTab));
301
397
  };
302
398
 
303
399
  return (
304
400
  <div className="base-js-link-picker">
401
+
305
402
  <style>{linkPickerStyles}</style>
306
403
 
307
404
  <div className="base-js-link-picker__control">
405
+
308
406
  <LinkControl
309
407
  value={{
310
408
  url: localUrl,
311
409
  opensInNewTab: localLinkTarget,
312
- id: localId,
410
+ id: postId || undefined,
313
411
  }}
314
412
  onChange={(updatedValue) => {
315
413
  handleLinkChange(updatedValue);
316
414
  }}
317
- onBlur={() => {
318
- setAttributes({
319
- [linkRef]: {
320
- url: localId
321
- ? ''
322
- : localUrl
323
- ? encodeURI(safeDecodeURI(localUrl))
324
- : '',
325
- linkTarget: localLinkTarget
326
- ? '_blank'
327
- : undefined,
328
- id: localId
329
- ? localId
330
- : undefined,
331
- },
332
- });
333
- }}
334
415
  />
416
+
335
417
  </div>
336
418
 
337
419
  {hasLinkData && (
338
420
  <div className="base-js-link-picker__status-wrapper">
421
+
339
422
  <div className="base-js-link-picker__status">
423
+
340
424
  <Icon
341
425
  icon={isInternalPost ? update : external}
342
426
  size={18}
343
427
  />
344
428
 
345
429
  <span>{statusLabel}</span>
430
+
346
431
  </div>
347
432
 
348
433
  {isInternalPost && linkedPostStatus.isLoading && (
@@ -353,13 +438,18 @@ export const LinkPicker = ({ setAttributes, at, linkRef = 'link' }) => {
353
438
 
354
439
  {shouldShowBrokenLinkStatus && (
355
440
  <div className="base-js-link-picker__status-detail">
441
+
356
442
  <BrokenLinkBadge
357
443
  message={brokenLinkMessage}
358
444
  />
445
+
359
446
  </div>
360
447
  )}
448
+
361
449
  </div>
362
450
  )}
451
+
363
452
  </div>
364
453
  );
454
+
365
455
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "base-js-sw",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Reusable Gutenberg block components for WordPress projects",
5
5
  "main": "index.js",
6
6
  "author": "Shape Works",