base-js-sw 1.0.9 → 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 +336 -36
- package/package.json +1 -1
package/components/LinkPicker.js
CHANGED
|
@@ -1,65 +1,365 @@
|
|
|
1
1
|
import { __experimentalLinkControl as LinkControl } from '@wordpress/blockEditor';
|
|
2
|
+
import { Icon } from '@wordpress/components';
|
|
3
|
+
import { external, update } from '@wordpress/icons';
|
|
2
4
|
import { safeDecodeURI } from '@wordpress/url';
|
|
3
5
|
import { useState, useEffect } from 'react';
|
|
4
|
-
|
|
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
|
+
|
|
27
|
+
const linkPickerStyles = `
|
|
28
|
+
.base-js-link-picker {
|
|
29
|
+
width: 100%;
|
|
30
|
+
max-width: 100%;
|
|
31
|
+
min-width: 0;
|
|
32
|
+
overflow-x: hidden;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.base-js-link-picker .block-editor-link-control {
|
|
36
|
+
width: 100%;
|
|
37
|
+
max-width: 100% !important;
|
|
38
|
+
min-width: 0 !important;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.base-js-link-picker .block-editor-link-control__search-input {
|
|
42
|
+
margin: 0 !important;
|
|
43
|
+
min-width: 0 !important;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.base-js-link-picker .components-input-control__container {
|
|
47
|
+
width: 90% !important;
|
|
48
|
+
max-width: 100%;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.base-js-link-picker__status {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
gap: 8px;
|
|
55
|
+
margin-top: 12px;
|
|
56
|
+
color: #757575;
|
|
57
|
+
font-size: 13px;
|
|
58
|
+
line-height: 1.4;
|
|
59
|
+
width: 100%;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.base-js-link-picker__status svg {
|
|
63
|
+
flex: 0 0 auto;
|
|
64
|
+
color: inherit;
|
|
65
|
+
fill: currentColor;
|
|
66
|
+
stroke: currentColor;
|
|
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
|
+
}
|
|
98
|
+
`;
|
|
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
|
+
};
|
|
5
153
|
|
|
6
154
|
export const LinkPicker = ({ setAttributes, at, linkRef = 'link' }) => {
|
|
7
|
-
// Get the current link object based on linkRef or fallback to an empty object
|
|
8
155
|
const linkObj = at[linkRef] || {};
|
|
9
156
|
const { url, linkTarget, id } = linkObj;
|
|
10
157
|
const opensInNewTab = linkTarget === '_blank';
|
|
11
158
|
|
|
12
|
-
// Local state to track temporary URL, ID, and link target
|
|
13
159
|
const [localUrl, setLocalUrl] = useState(url || '');
|
|
14
160
|
const [localId, setLocalId] = useState(id || '');
|
|
15
161
|
const [localLinkTarget, setLocalLinkTarget] = useState(opensInNewTab);
|
|
16
162
|
|
|
17
|
-
|
|
163
|
+
const [linkedPostStatus, setLinkedPostStatus] = useState({
|
|
164
|
+
isLoading: false,
|
|
165
|
+
result: null,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const isInternalPost = Boolean(localId);
|
|
169
|
+
const hasLinkData = Boolean(localUrl || localId);
|
|
170
|
+
|
|
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
|
+
|
|
18
188
|
useEffect(() => {
|
|
19
189
|
setAttributes({
|
|
20
190
|
[linkRef]: {
|
|
21
|
-
url: localUrl
|
|
22
|
-
|
|
23
|
-
|
|
191
|
+
url: localUrl
|
|
192
|
+
? encodeURI(safeDecodeURI(localUrl))
|
|
193
|
+
: '',
|
|
194
|
+
linkTarget: localLinkTarget
|
|
195
|
+
? '_blank'
|
|
196
|
+
: undefined,
|
|
197
|
+
id: localId
|
|
198
|
+
? localId
|
|
199
|
+
: undefined,
|
|
24
200
|
},
|
|
25
201
|
});
|
|
26
|
-
}, [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]);
|
|
27
285
|
|
|
28
286
|
const handleLinkChange = (updatedValue) => {
|
|
29
|
-
|
|
30
|
-
const { url, opensInNewTab, id, kind } = updatedValue;
|
|
287
|
+
const { url, opensInNewTab, id } = updatedValue;
|
|
31
288
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
289
|
+
const nextUrl = url || '';
|
|
290
|
+
const isPostSelection = Boolean(id);
|
|
291
|
+
|
|
292
|
+
if (isPostSelection) {
|
|
35
293
|
setLocalId(id);
|
|
36
|
-
setLocalUrl(
|
|
294
|
+
setLocalUrl(nextUrl);
|
|
37
295
|
} else {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
setLocalId(''); // Clear the ID since we are using the URL
|
|
296
|
+
setLocalUrl(nextUrl);
|
|
297
|
+
setLocalId('');
|
|
41
298
|
}
|
|
42
299
|
|
|
43
|
-
|
|
44
|
-
setLocalLinkTarget(opensInNewTab);
|
|
300
|
+
setLocalLinkTarget(Boolean(opensInNewTab));
|
|
45
301
|
};
|
|
46
302
|
|
|
47
303
|
return (
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
304
|
+
<div className="base-js-link-picker">
|
|
305
|
+
<style>{linkPickerStyles}</style>
|
|
306
|
+
|
|
307
|
+
<div className="base-js-link-picker__control">
|
|
308
|
+
<LinkControl
|
|
309
|
+
value={{
|
|
310
|
+
url: localUrl,
|
|
311
|
+
opensInNewTab: localLinkTarget,
|
|
312
|
+
id: localId,
|
|
313
|
+
}}
|
|
314
|
+
onChange={(updatedValue) => {
|
|
315
|
+
handleLinkChange(updatedValue);
|
|
316
|
+
}}
|
|
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
|
+
/>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
{hasLinkData && (
|
|
338
|
+
<div className="base-js-link-picker__status-wrapper">
|
|
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…
|
|
351
|
+
</div>
|
|
352
|
+
)}
|
|
353
|
+
|
|
354
|
+
{shouldShowBrokenLinkStatus && (
|
|
355
|
+
<div className="base-js-link-picker__status-detail">
|
|
356
|
+
<BrokenLinkBadge
|
|
357
|
+
message={brokenLinkMessage}
|
|
358
|
+
/>
|
|
359
|
+
</div>
|
|
360
|
+
)}
|
|
361
|
+
</div>
|
|
362
|
+
)}
|
|
363
|
+
</div>
|
|
64
364
|
);
|
|
65
|
-
};
|
|
365
|
+
};
|