@contentful/field-editor-shared 2.16.0 → 2.17.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.
|
@@ -30,6 +30,9 @@ _export(exports, {
|
|
|
30
30
|
getFieldValue: function() {
|
|
31
31
|
return getFieldValue;
|
|
32
32
|
},
|
|
33
|
+
getResolvedImageUrl: function() {
|
|
34
|
+
return getResolvedImageUrl;
|
|
35
|
+
},
|
|
33
36
|
isAssetField: function() {
|
|
34
37
|
return isAssetField;
|
|
35
38
|
},
|
|
@@ -236,3 +239,31 @@ const getEntryImage = async ({ entry, contentType, localeCode }, getAsset)=>{
|
|
|
236
239
|
return null;
|
|
237
240
|
}
|
|
238
241
|
};
|
|
242
|
+
const getResolvedImageUrl = (url, params)=>{
|
|
243
|
+
try {
|
|
244
|
+
const urlToParse = url.startsWith('//') ? `https:${url}` : url;
|
|
245
|
+
const parsedUrl = new URL(urlToParse);
|
|
246
|
+
if (parsedUrl.hostname.startsWith('downloads.')) {
|
|
247
|
+
parsedUrl.hostname = parsedUrl.hostname.replace(/^downloads\./, 'images.');
|
|
248
|
+
}
|
|
249
|
+
if (params) {
|
|
250
|
+
Object.entries(params).forEach(([key, value])=>{
|
|
251
|
+
if (value !== undefined) {
|
|
252
|
+
parsedUrl.searchParams.set(key, value.toString());
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
const result = parsedUrl.toString();
|
|
257
|
+
return url.startsWith('//') ? result.replace(/^https:/, '') : result;
|
|
258
|
+
} catch {
|
|
259
|
+
if (!params) return url;
|
|
260
|
+
const searchParams = new URLSearchParams();
|
|
261
|
+
Object.entries(params).forEach(([key, value])=>{
|
|
262
|
+
if (value !== undefined) {
|
|
263
|
+
searchParams.set(key, value.toString());
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
const queryString = searchParams.toString();
|
|
267
|
+
return queryString ? `${url}?${queryString}` : url;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
@@ -149,3 +149,139 @@ describe('getEntityStatus', ()=>{
|
|
|
149
149
|
});
|
|
150
150
|
});
|
|
151
151
|
});
|
|
152
|
+
describe('getResolvedImageUrl', ()=>{
|
|
153
|
+
describe('URL parsing and domain replacement', ()=>{
|
|
154
|
+
test('replaces downloads.* with images.*', ()=>{
|
|
155
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.example.com/space/asset.jpg');
|
|
156
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg');
|
|
157
|
+
});
|
|
158
|
+
test('handles protocol-relative URLs', ()=>{
|
|
159
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('//downloads.example.com/space/asset.jpg');
|
|
160
|
+
expect(result).toBe('//images.example.com/space/asset.jpg');
|
|
161
|
+
});
|
|
162
|
+
test('does not modify URLs that do not start with downloads.', ()=>{
|
|
163
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://example.com/image.jpg');
|
|
164
|
+
expect(result).toBe('https://example.com/image.jpg');
|
|
165
|
+
});
|
|
166
|
+
test('does not modify images.* URLs', ()=>{
|
|
167
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://images.example.com/space/asset.jpg');
|
|
168
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe('query parameters', ()=>{
|
|
172
|
+
test('adds width parameter', ()=>{
|
|
173
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.example.com/space/asset.jpg', {
|
|
174
|
+
w: 100
|
|
175
|
+
});
|
|
176
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg?w=100');
|
|
177
|
+
});
|
|
178
|
+
test('adds height parameter', ()=>{
|
|
179
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.example.com/space/asset.jpg', {
|
|
180
|
+
h: 200
|
|
181
|
+
});
|
|
182
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg?h=200');
|
|
183
|
+
});
|
|
184
|
+
test('adds fit parameter', ()=>{
|
|
185
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.example.com/space/asset.jpg', {
|
|
186
|
+
fit: 'thumb'
|
|
187
|
+
});
|
|
188
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg?fit=thumb');
|
|
189
|
+
});
|
|
190
|
+
test('adds multiple parameters', ()=>{
|
|
191
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.example.com/space/asset.jpg', {
|
|
192
|
+
w: 100,
|
|
193
|
+
h: 200,
|
|
194
|
+
fit: 'thumb'
|
|
195
|
+
});
|
|
196
|
+
expect(result).toContain('w=100');
|
|
197
|
+
expect(result).toContain('h=200');
|
|
198
|
+
expect(result).toContain('fit=thumb');
|
|
199
|
+
expect(result).toContain('images.example.com');
|
|
200
|
+
});
|
|
201
|
+
test('skips undefined parameters', ()=>{
|
|
202
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.example.com/space/asset.jpg', {
|
|
203
|
+
w: 100,
|
|
204
|
+
h: undefined,
|
|
205
|
+
fit: 'thumb'
|
|
206
|
+
});
|
|
207
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg?w=100&fit=thumb');
|
|
208
|
+
});
|
|
209
|
+
test('returns URL without query string when no params provided', ()=>{
|
|
210
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.example.com/space/asset.jpg');
|
|
211
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg');
|
|
212
|
+
});
|
|
213
|
+
test('returns URL without query string when all params are undefined', ()=>{
|
|
214
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.example.com/space/asset.jpg', {
|
|
215
|
+
w: undefined,
|
|
216
|
+
h: undefined,
|
|
217
|
+
fit: undefined
|
|
218
|
+
});
|
|
219
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
describe('relative URL fallback', ()=>{
|
|
223
|
+
test('returns relative URL unchanged when no params provided', ()=>{
|
|
224
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('/assets/image.jpg');
|
|
225
|
+
expect(result).toBe('/assets/image.jpg');
|
|
226
|
+
});
|
|
227
|
+
test('appends query params to relative URLs', ()=>{
|
|
228
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('/assets/image.jpg', {
|
|
229
|
+
w: 100,
|
|
230
|
+
h: 200
|
|
231
|
+
});
|
|
232
|
+
expect(result).toBe('/assets/image.jpg?w=100&h=200');
|
|
233
|
+
});
|
|
234
|
+
test('handles relative URLs with undefined params', ()=>{
|
|
235
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('/assets/image.jpg', {
|
|
236
|
+
w: 100,
|
|
237
|
+
h: undefined
|
|
238
|
+
});
|
|
239
|
+
expect(result).toBe('/assets/image.jpg?w=100');
|
|
240
|
+
});
|
|
241
|
+
test('returns relative URL unchanged when all params are undefined', ()=>{
|
|
242
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('/assets/image.jpg', {
|
|
243
|
+
w: undefined,
|
|
244
|
+
h: undefined
|
|
245
|
+
});
|
|
246
|
+
expect(result).toBe('/assets/image.jpg');
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
describe('edge cases', ()=>{
|
|
250
|
+
test('preserves existing query parameters', ()=>{
|
|
251
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.example.com/space/asset.jpg?foo=bar', {
|
|
252
|
+
w: 100
|
|
253
|
+
});
|
|
254
|
+
expect(result).toContain('foo=bar');
|
|
255
|
+
expect(result).toContain('w=100');
|
|
256
|
+
});
|
|
257
|
+
test('handles URLs with fragments', ()=>{
|
|
258
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.example.com/space/asset.jpg#section', {
|
|
259
|
+
w: 100
|
|
260
|
+
});
|
|
261
|
+
expect(result).toContain('images.example.com');
|
|
262
|
+
expect(result).toContain('w=100');
|
|
263
|
+
expect(result).toContain('#section');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
describe('pattern-based domain replacement', ()=>{
|
|
267
|
+
test('replaces any downloads.* domain with images.*', ()=>{
|
|
268
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.example.com/space/asset.jpg');
|
|
269
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg');
|
|
270
|
+
});
|
|
271
|
+
test('handles protocol-relative downloads URLs', ()=>{
|
|
272
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('//downloads.example.com/space/asset.jpg');
|
|
273
|
+
expect(result).toBe('//images.example.com/space/asset.jpg');
|
|
274
|
+
});
|
|
275
|
+
test('adds query params to any downloads.* domain', ()=>{
|
|
276
|
+
const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.another-example.com/space/asset.jpg', {
|
|
277
|
+
w: 150,
|
|
278
|
+
h: 150,
|
|
279
|
+
fit: 'thumb'
|
|
280
|
+
});
|
|
281
|
+
expect(result).toContain('images.another-example.com');
|
|
282
|
+
expect(result).toContain('w=150');
|
|
283
|
+
expect(result).toContain('h=150');
|
|
284
|
+
expect(result).toContain('fit=thumb');
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -189,3 +189,31 @@ export const getEntryImage = async ({ entry, contentType, localeCode }, getAsset
|
|
|
189
189
|
return null;
|
|
190
190
|
}
|
|
191
191
|
};
|
|
192
|
+
export const getResolvedImageUrl = (url, params)=>{
|
|
193
|
+
try {
|
|
194
|
+
const urlToParse = url.startsWith('//') ? `https:${url}` : url;
|
|
195
|
+
const parsedUrl = new URL(urlToParse);
|
|
196
|
+
if (parsedUrl.hostname.startsWith('downloads.')) {
|
|
197
|
+
parsedUrl.hostname = parsedUrl.hostname.replace(/^downloads\./, 'images.');
|
|
198
|
+
}
|
|
199
|
+
if (params) {
|
|
200
|
+
Object.entries(params).forEach(([key, value])=>{
|
|
201
|
+
if (value !== undefined) {
|
|
202
|
+
parsedUrl.searchParams.set(key, value.toString());
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const result = parsedUrl.toString();
|
|
207
|
+
return url.startsWith('//') ? result.replace(/^https:/, '') : result;
|
|
208
|
+
} catch {
|
|
209
|
+
if (!params) return url;
|
|
210
|
+
const searchParams = new URLSearchParams();
|
|
211
|
+
Object.entries(params).forEach(([key, value])=>{
|
|
212
|
+
if (value !== undefined) {
|
|
213
|
+
searchParams.set(key, value.toString());
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
const queryString = searchParams.toString();
|
|
217
|
+
return queryString ? `${url}?${queryString}` : url;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getEntityStatus } from './entityHelpers';
|
|
1
|
+
import { getEntityStatus, getResolvedImageUrl } from './entityHelpers';
|
|
2
2
|
describe('getEntityStatus', ()=>{
|
|
3
3
|
function createEntity(props) {
|
|
4
4
|
return props;
|
|
@@ -145,3 +145,139 @@ describe('getEntityStatus', ()=>{
|
|
|
145
145
|
});
|
|
146
146
|
});
|
|
147
147
|
});
|
|
148
|
+
describe('getResolvedImageUrl', ()=>{
|
|
149
|
+
describe('URL parsing and domain replacement', ()=>{
|
|
150
|
+
test('replaces downloads.* with images.*', ()=>{
|
|
151
|
+
const result = getResolvedImageUrl('https://downloads.example.com/space/asset.jpg');
|
|
152
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg');
|
|
153
|
+
});
|
|
154
|
+
test('handles protocol-relative URLs', ()=>{
|
|
155
|
+
const result = getResolvedImageUrl('//downloads.example.com/space/asset.jpg');
|
|
156
|
+
expect(result).toBe('//images.example.com/space/asset.jpg');
|
|
157
|
+
});
|
|
158
|
+
test('does not modify URLs that do not start with downloads.', ()=>{
|
|
159
|
+
const result = getResolvedImageUrl('https://example.com/image.jpg');
|
|
160
|
+
expect(result).toBe('https://example.com/image.jpg');
|
|
161
|
+
});
|
|
162
|
+
test('does not modify images.* URLs', ()=>{
|
|
163
|
+
const result = getResolvedImageUrl('https://images.example.com/space/asset.jpg');
|
|
164
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
describe('query parameters', ()=>{
|
|
168
|
+
test('adds width parameter', ()=>{
|
|
169
|
+
const result = getResolvedImageUrl('https://downloads.example.com/space/asset.jpg', {
|
|
170
|
+
w: 100
|
|
171
|
+
});
|
|
172
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg?w=100');
|
|
173
|
+
});
|
|
174
|
+
test('adds height parameter', ()=>{
|
|
175
|
+
const result = getResolvedImageUrl('https://downloads.example.com/space/asset.jpg', {
|
|
176
|
+
h: 200
|
|
177
|
+
});
|
|
178
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg?h=200');
|
|
179
|
+
});
|
|
180
|
+
test('adds fit parameter', ()=>{
|
|
181
|
+
const result = getResolvedImageUrl('https://downloads.example.com/space/asset.jpg', {
|
|
182
|
+
fit: 'thumb'
|
|
183
|
+
});
|
|
184
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg?fit=thumb');
|
|
185
|
+
});
|
|
186
|
+
test('adds multiple parameters', ()=>{
|
|
187
|
+
const result = getResolvedImageUrl('https://downloads.example.com/space/asset.jpg', {
|
|
188
|
+
w: 100,
|
|
189
|
+
h: 200,
|
|
190
|
+
fit: 'thumb'
|
|
191
|
+
});
|
|
192
|
+
expect(result).toContain('w=100');
|
|
193
|
+
expect(result).toContain('h=200');
|
|
194
|
+
expect(result).toContain('fit=thumb');
|
|
195
|
+
expect(result).toContain('images.example.com');
|
|
196
|
+
});
|
|
197
|
+
test('skips undefined parameters', ()=>{
|
|
198
|
+
const result = getResolvedImageUrl('https://downloads.example.com/space/asset.jpg', {
|
|
199
|
+
w: 100,
|
|
200
|
+
h: undefined,
|
|
201
|
+
fit: 'thumb'
|
|
202
|
+
});
|
|
203
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg?w=100&fit=thumb');
|
|
204
|
+
});
|
|
205
|
+
test('returns URL without query string when no params provided', ()=>{
|
|
206
|
+
const result = getResolvedImageUrl('https://downloads.example.com/space/asset.jpg');
|
|
207
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg');
|
|
208
|
+
});
|
|
209
|
+
test('returns URL without query string when all params are undefined', ()=>{
|
|
210
|
+
const result = getResolvedImageUrl('https://downloads.example.com/space/asset.jpg', {
|
|
211
|
+
w: undefined,
|
|
212
|
+
h: undefined,
|
|
213
|
+
fit: undefined
|
|
214
|
+
});
|
|
215
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
describe('relative URL fallback', ()=>{
|
|
219
|
+
test('returns relative URL unchanged when no params provided', ()=>{
|
|
220
|
+
const result = getResolvedImageUrl('/assets/image.jpg');
|
|
221
|
+
expect(result).toBe('/assets/image.jpg');
|
|
222
|
+
});
|
|
223
|
+
test('appends query params to relative URLs', ()=>{
|
|
224
|
+
const result = getResolvedImageUrl('/assets/image.jpg', {
|
|
225
|
+
w: 100,
|
|
226
|
+
h: 200
|
|
227
|
+
});
|
|
228
|
+
expect(result).toBe('/assets/image.jpg?w=100&h=200');
|
|
229
|
+
});
|
|
230
|
+
test('handles relative URLs with undefined params', ()=>{
|
|
231
|
+
const result = getResolvedImageUrl('/assets/image.jpg', {
|
|
232
|
+
w: 100,
|
|
233
|
+
h: undefined
|
|
234
|
+
});
|
|
235
|
+
expect(result).toBe('/assets/image.jpg?w=100');
|
|
236
|
+
});
|
|
237
|
+
test('returns relative URL unchanged when all params are undefined', ()=>{
|
|
238
|
+
const result = getResolvedImageUrl('/assets/image.jpg', {
|
|
239
|
+
w: undefined,
|
|
240
|
+
h: undefined
|
|
241
|
+
});
|
|
242
|
+
expect(result).toBe('/assets/image.jpg');
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
describe('edge cases', ()=>{
|
|
246
|
+
test('preserves existing query parameters', ()=>{
|
|
247
|
+
const result = getResolvedImageUrl('https://downloads.example.com/space/asset.jpg?foo=bar', {
|
|
248
|
+
w: 100
|
|
249
|
+
});
|
|
250
|
+
expect(result).toContain('foo=bar');
|
|
251
|
+
expect(result).toContain('w=100');
|
|
252
|
+
});
|
|
253
|
+
test('handles URLs with fragments', ()=>{
|
|
254
|
+
const result = getResolvedImageUrl('https://downloads.example.com/space/asset.jpg#section', {
|
|
255
|
+
w: 100
|
|
256
|
+
});
|
|
257
|
+
expect(result).toContain('images.example.com');
|
|
258
|
+
expect(result).toContain('w=100');
|
|
259
|
+
expect(result).toContain('#section');
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
describe('pattern-based domain replacement', ()=>{
|
|
263
|
+
test('replaces any downloads.* domain with images.*', ()=>{
|
|
264
|
+
const result = getResolvedImageUrl('https://downloads.example.com/space/asset.jpg');
|
|
265
|
+
expect(result).toBe('https://images.example.com/space/asset.jpg');
|
|
266
|
+
});
|
|
267
|
+
test('handles protocol-relative downloads URLs', ()=>{
|
|
268
|
+
const result = getResolvedImageUrl('//downloads.example.com/space/asset.jpg');
|
|
269
|
+
expect(result).toBe('//images.example.com/space/asset.jpg');
|
|
270
|
+
});
|
|
271
|
+
test('adds query params to any downloads.* domain', ()=>{
|
|
272
|
+
const result = getResolvedImageUrl('https://downloads.another-example.com/space/asset.jpg', {
|
|
273
|
+
w: 150,
|
|
274
|
+
h: 150,
|
|
275
|
+
fit: 'thumb'
|
|
276
|
+
});
|
|
277
|
+
expect(result).toContain('images.another-example.com');
|
|
278
|
+
expect(result).toContain('w=150');
|
|
279
|
+
expect(result).toContain('h=150');
|
|
280
|
+
expect(result).toContain('fit=thumb');
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
1
2
|
import { Asset, ContentType, ContentTypeField, Entry, File } from '../typesEntity';
|
|
2
3
|
export declare function getFieldValue({
|
|
3
4
|
/**
|
|
@@ -80,4 +81,9 @@ export declare const getEntryImage: ({ entry, contentType, localeCode, }: {
|
|
|
80
81
|
localeCode: string;
|
|
81
82
|
defaultLocaleCode: string;
|
|
82
83
|
}, getAsset: (assetId: string) => Promise<unknown>) => Promise<null | File>;
|
|
84
|
+
export declare const getResolvedImageUrl: (url: string, params?: {
|
|
85
|
+
w?: number;
|
|
86
|
+
h?: number;
|
|
87
|
+
fit?: string;
|
|
88
|
+
}) => string;
|
|
83
89
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/field-editor-shared",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.17.0",
|
|
4
4
|
"main": "dist/cjs/index.js",
|
|
5
5
|
"module": "dist/esm/index.js",
|
|
6
6
|
"types": "dist/types/index.d.ts",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"publishConfig": {
|
|
61
61
|
"registry": "https://npm.pkg.github.com/"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "c85b1e8cd6772eb796ed2b22ebb778c717f4a7f4"
|
|
64
64
|
}
|