base-js-sw 1.0.10 → 1.0.11
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/components/LinkPicker.js +263 -33
- package/package.json +1 -1
package/components/LinkPicker.js
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
import { __experimentalLinkControl as LinkControl } from '@wordpress/blockEditor';
|
|
2
|
-
import { Icon
|
|
2
|
+
import { Icon } from '@wordpress/components';
|
|
3
3
|
import { external, update } from '@wordpress/icons';
|
|
4
4
|
import { safeDecodeURI } from '@wordpress/url';
|
|
5
5
|
import { useState, useEffect } from 'react';
|
|
6
6
|
|
|
7
|
+
const linkedPostStatusCache = {};
|
|
8
|
+
|
|
9
|
+
const STATUSES = [
|
|
10
|
+
'draft',
|
|
11
|
+
'private',
|
|
12
|
+
'trash',
|
|
13
|
+
'pending',
|
|
14
|
+
'future',
|
|
15
|
+
'auto-draft',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const STATUS_MESSAGES = {
|
|
19
|
+
draft: 'Post is a draft.',
|
|
20
|
+
private: 'Post is private.',
|
|
21
|
+
trash: 'Post is in the trash.',
|
|
22
|
+
pending: 'Post is pending review.',
|
|
23
|
+
future: 'Post is scheduled.',
|
|
24
|
+
'auto-draft': 'Post is an auto draft.',
|
|
25
|
+
};
|
|
26
|
+
|
|
7
27
|
const linkPickerStyles = `
|
|
8
28
|
.base-js-link-picker {
|
|
9
29
|
width: 100%;
|
|
@@ -34,9 +54,9 @@ const linkPickerStyles = `
|
|
|
34
54
|
gap: 8px;
|
|
35
55
|
margin-top: 12px;
|
|
36
56
|
color: #757575;
|
|
37
|
-
cursor: help;
|
|
38
57
|
font-size: 13px;
|
|
39
58
|
line-height: 1.4;
|
|
59
|
+
width: 100%;
|
|
40
60
|
}
|
|
41
61
|
|
|
42
62
|
.base-js-link-picker__status svg {
|
|
@@ -45,91 +65,301 @@ const linkPickerStyles = `
|
|
|
45
65
|
fill: currentColor;
|
|
46
66
|
stroke: currentColor;
|
|
47
67
|
}
|
|
68
|
+
|
|
69
|
+
.base-js-link-picker__status-detail {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: 8px;
|
|
73
|
+
margin-top: 8px;
|
|
74
|
+
width: 100%;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.base-js-link-picker__status-detail .components-badge {
|
|
78
|
+
max-width: 100%;
|
|
79
|
+
width: 100%;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.base-js-link-picker__status-detail .base-js-link-picker__broken-link {
|
|
83
|
+
max-width: 100%;
|
|
84
|
+
width: 100%;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.base-js-link-picker__broken-link .components-badge {
|
|
88
|
+
max-width: 100%;
|
|
89
|
+
width: 100%;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.base-js-link-picker__status-loading {
|
|
93
|
+
margin-top: 8px;
|
|
94
|
+
font-size: 13px;
|
|
95
|
+
line-height: 1.4;
|
|
96
|
+
color: #757575;
|
|
97
|
+
}
|
|
48
98
|
`;
|
|
49
99
|
|
|
100
|
+
const getResultStatus = (result) => {
|
|
101
|
+
return String(
|
|
102
|
+
result?.status ||
|
|
103
|
+
result?.post_status ||
|
|
104
|
+
result?.postStatus ||
|
|
105
|
+
''
|
|
106
|
+
).toLowerCase();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const getLinkedPostStatusMessage = (result) => {
|
|
110
|
+
const status = getResultStatus(result);
|
|
111
|
+
|
|
112
|
+
if (STATUS_MESSAGES[status]) {
|
|
113
|
+
return STATUS_MESSAGES[status];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (result?.message) {
|
|
117
|
+
return result.message;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return 'Linked post needs review.';
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const BrokenLinkBadge = ({ message }) => {
|
|
124
|
+
return (
|
|
125
|
+
<div className="base-js-link-picker__broken-link">
|
|
126
|
+
<span className="components-badge is-warning has-icon">
|
|
127
|
+
<span className="components-badge__flex-wrapper">
|
|
128
|
+
<svg
|
|
129
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
130
|
+
viewBox="0 0 24 24"
|
|
131
|
+
width="16"
|
|
132
|
+
height="16"
|
|
133
|
+
fill="currentColor"
|
|
134
|
+
className="components-badge__icon"
|
|
135
|
+
aria-hidden="true"
|
|
136
|
+
focusable="false"
|
|
137
|
+
>
|
|
138
|
+
<path
|
|
139
|
+
fillRule="evenodd"
|
|
140
|
+
clipRule="evenodd"
|
|
141
|
+
d="M5.5 12a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0ZM12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm-.75 12v-1.5h1.5V16h-1.5Zm0-8v5h1.5V8h-1.5Z"
|
|
142
|
+
/>
|
|
143
|
+
</svg>
|
|
144
|
+
|
|
145
|
+
<span className="components-badge__content">
|
|
146
|
+
{message}
|
|
147
|
+
</span>
|
|
148
|
+
</span>
|
|
149
|
+
</span>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
50
154
|
export const LinkPicker = ({ setAttributes, at, linkRef = 'link' }) => {
|
|
51
|
-
// Get the current link object based on linkRef or fallback to an empty object
|
|
52
155
|
const linkObj = at[linkRef] || {};
|
|
53
156
|
const { url, linkTarget, id } = linkObj;
|
|
54
157
|
const opensInNewTab = linkTarget === '_blank';
|
|
55
158
|
|
|
56
|
-
// Local state to track temporary URL, ID, and link target
|
|
57
159
|
const [localUrl, setLocalUrl] = useState(url || '');
|
|
58
160
|
const [localId, setLocalId] = useState(id || '');
|
|
59
161
|
const [localLinkTarget, setLocalLinkTarget] = useState(opensInNewTab);
|
|
162
|
+
|
|
163
|
+
const [linkedPostStatus, setLinkedPostStatus] = useState({
|
|
164
|
+
isLoading: false,
|
|
165
|
+
result: null,
|
|
166
|
+
});
|
|
167
|
+
|
|
60
168
|
const isInternalPost = Boolean(localId);
|
|
61
169
|
const hasLinkData = Boolean(localUrl || localId);
|
|
62
|
-
const statusLabel = isInternalPost ? 'Dynamic WordPress link' : 'External link';
|
|
63
|
-
const statusTooltip = isInternalPost
|
|
64
|
-
? 'This is a dynamic link to a page within the website. If the page’s URL changes it will update automatically'
|
|
65
|
-
: 'This is a static link and does not auto-update.';
|
|
66
170
|
|
|
67
|
-
|
|
171
|
+
const statusLabel = isInternalPost
|
|
172
|
+
? 'Dynamic WordPress link'
|
|
173
|
+
: 'External link';
|
|
174
|
+
|
|
175
|
+
const currentStatus = linkedPostStatus.result
|
|
176
|
+
? getResultStatus(linkedPostStatus.result)
|
|
177
|
+
: '';
|
|
178
|
+
|
|
179
|
+
const brokenLinkMessage = linkedPostStatus.result
|
|
180
|
+
? getLinkedPostStatusMessage(linkedPostStatus.result)
|
|
181
|
+
: '';
|
|
182
|
+
|
|
183
|
+
const shouldShowBrokenLinkStatus =
|
|
184
|
+
isInternalPost &&
|
|
185
|
+
linkedPostStatus.result &&
|
|
186
|
+
STATUSES.includes(currentStatus);
|
|
187
|
+
|
|
68
188
|
useEffect(() => {
|
|
69
189
|
setAttributes({
|
|
70
190
|
[linkRef]: {
|
|
71
|
-
url: localUrl
|
|
72
|
-
|
|
73
|
-
|
|
191
|
+
url: localUrl
|
|
192
|
+
? encodeURI(safeDecodeURI(localUrl))
|
|
193
|
+
: '',
|
|
194
|
+
linkTarget: localLinkTarget
|
|
195
|
+
? '_blank'
|
|
196
|
+
: undefined,
|
|
197
|
+
id: localId
|
|
198
|
+
? localId
|
|
199
|
+
: undefined,
|
|
74
200
|
},
|
|
75
201
|
});
|
|
76
|
-
}, [localUrl, localId, localLinkTarget]);
|
|
202
|
+
}, [localUrl, localId, localLinkTarget]);
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
const numericPostId = parseInt(localId, 10);
|
|
206
|
+
|
|
207
|
+
const hasValidPostId =
|
|
208
|
+
Number.isInteger(numericPostId) &&
|
|
209
|
+
numericPostId > 0;
|
|
210
|
+
|
|
211
|
+
if (!hasValidPostId) {
|
|
212
|
+
setLinkedPostStatus({
|
|
213
|
+
isLoading: false,
|
|
214
|
+
result: null,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (linkedPostStatusCache[numericPostId]) {
|
|
221
|
+
setLinkedPostStatus({
|
|
222
|
+
isLoading: false,
|
|
223
|
+
result: linkedPostStatusCache[numericPostId],
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const apiFetch = window.wp?.apiFetch;
|
|
230
|
+
|
|
231
|
+
if (!apiFetch) {
|
|
232
|
+
setLinkedPostStatus({
|
|
233
|
+
isLoading: false,
|
|
234
|
+
result: {
|
|
235
|
+
status: 'draft',
|
|
236
|
+
message: 'Could not check this post.',
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let isMounted = true;
|
|
244
|
+
|
|
245
|
+
setLinkedPostStatus({
|
|
246
|
+
isLoading: true,
|
|
247
|
+
result: null,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
apiFetch({
|
|
251
|
+
path: `/theme/v1/linked-post-validator/post/${numericPostId}`,
|
|
252
|
+
})
|
|
253
|
+
.then((result) => {
|
|
254
|
+
if (!isMounted) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
linkedPostStatusCache[numericPostId] = result;
|
|
259
|
+
|
|
260
|
+
setLinkedPostStatus({
|
|
261
|
+
isLoading: false,
|
|
262
|
+
result,
|
|
263
|
+
});
|
|
264
|
+
})
|
|
265
|
+
.catch(() => {
|
|
266
|
+
if (!isMounted) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const errorResult = {
|
|
271
|
+
status: 'draft',
|
|
272
|
+
message: 'Could not check this post.',
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
setLinkedPostStatus({
|
|
276
|
+
isLoading: false,
|
|
277
|
+
result: errorResult,
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return () => {
|
|
282
|
+
isMounted = false;
|
|
283
|
+
};
|
|
284
|
+
}, [localId]);
|
|
77
285
|
|
|
78
286
|
const handleLinkChange = (updatedValue) => {
|
|
79
287
|
const { url, opensInNewTab, id } = updatedValue;
|
|
288
|
+
|
|
80
289
|
const nextUrl = url || '';
|
|
81
290
|
const isPostSelection = Boolean(id);
|
|
82
291
|
|
|
83
|
-
// Update ID or URL depending on whether it's an internal post or an external link/anchor
|
|
84
292
|
if (isPostSelection) {
|
|
85
|
-
// Internal post, use ID
|
|
86
293
|
setLocalId(id);
|
|
87
294
|
setLocalUrl(nextUrl);
|
|
88
295
|
} else {
|
|
89
|
-
// External or anchor link, use URL
|
|
90
296
|
setLocalUrl(nextUrl);
|
|
91
|
-
setLocalId('');
|
|
297
|
+
setLocalId('');
|
|
92
298
|
}
|
|
93
299
|
|
|
94
|
-
// Update the new tab option
|
|
95
300
|
setLocalLinkTarget(Boolean(opensInNewTab));
|
|
96
301
|
};
|
|
97
302
|
|
|
98
303
|
return (
|
|
99
304
|
<div className="base-js-link-picker">
|
|
100
305
|
<style>{linkPickerStyles}</style>
|
|
306
|
+
|
|
101
307
|
<div className="base-js-link-picker__control">
|
|
102
308
|
<LinkControl
|
|
103
|
-
value={{
|
|
309
|
+
value={{
|
|
310
|
+
url: localUrl,
|
|
311
|
+
opensInNewTab: localLinkTarget,
|
|
312
|
+
id: localId,
|
|
313
|
+
}}
|
|
104
314
|
onChange={(updatedValue) => {
|
|
105
315
|
handleLinkChange(updatedValue);
|
|
106
316
|
}}
|
|
107
317
|
onBlur={() => {
|
|
108
|
-
// Save on blur to ensure the value is saved when clicking away
|
|
109
318
|
setAttributes({
|
|
110
319
|
[linkRef]: {
|
|
111
|
-
url: localId
|
|
112
|
-
|
|
113
|
-
|
|
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,
|
|
114
331
|
},
|
|
115
332
|
});
|
|
116
333
|
}}
|
|
117
334
|
/>
|
|
118
335
|
</div>
|
|
336
|
+
|
|
119
337
|
{hasLinkData && (
|
|
120
338
|
<div className="base-js-link-picker__status-wrapper">
|
|
121
|
-
<
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
339
|
+
<div className="base-js-link-picker__status">
|
|
340
|
+
<Icon
|
|
341
|
+
icon={isInternalPost ? update : external}
|
|
342
|
+
size={18}
|
|
343
|
+
/>
|
|
344
|
+
|
|
345
|
+
<span>{statusLabel}</span>
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
{isInternalPost && linkedPostStatus.isLoading && (
|
|
349
|
+
<div className="base-js-link-picker__status-loading">
|
|
350
|
+
Checking linked post status…
|
|
129
351
|
</div>
|
|
130
|
-
|
|
352
|
+
)}
|
|
353
|
+
|
|
354
|
+
{shouldShowBrokenLinkStatus && (
|
|
355
|
+
<div className="base-js-link-picker__status-detail">
|
|
356
|
+
<BrokenLinkBadge
|
|
357
|
+
message={brokenLinkMessage}
|
|
358
|
+
/>
|
|
359
|
+
</div>
|
|
360
|
+
)}
|
|
131
361
|
</div>
|
|
132
362
|
)}
|
|
133
363
|
</div>
|
|
134
364
|
);
|
|
135
|
-
};
|
|
365
|
+
};
|