@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.16.0",
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": "e685baec786ae9c9a6e0660bdb17005304646593"
63
+ "gitHead": "c85b1e8cd6772eb796ed2b22ebb778c717f4a7f4"
64
64
  }