@contentful/field-editor-shared 2.16.0 → 2.17.0-alpha.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,33 @@ const getEntryImage = async ({ entry, contentType, localeCode }, getAsset)=>{
236
239
  return null;
237
240
  }
238
241
  };
242
+ const DOWNLOADS_ENDPOINT = 'downloads.ctfassets.net';
243
+ const TRANSFORMATIONS_ENDPOINT = 'images.ctfassets.net';
244
+ const getResolvedImageUrl = (url, params)=>{
245
+ try {
246
+ const urlToParse = url.startsWith('//') ? `https:${url}` : url;
247
+ const parsedUrl = new URL(urlToParse);
248
+ if (parsedUrl.hostname === DOWNLOADS_ENDPOINT) {
249
+ parsedUrl.hostname = TRANSFORMATIONS_ENDPOINT;
250
+ }
251
+ if (params) {
252
+ Object.entries(params).forEach(([key, value])=>{
253
+ if (value !== undefined) {
254
+ parsedUrl.searchParams.set(key, value.toString());
255
+ }
256
+ });
257
+ }
258
+ const result = parsedUrl.toString();
259
+ return url.startsWith('//') ? result.replace(/^https:/, '') : result;
260
+ } catch {
261
+ if (!params) return url;
262
+ const searchParams = new URLSearchParams();
263
+ Object.entries(params).forEach(([key, value])=>{
264
+ if (value !== undefined) {
265
+ searchParams.set(key, value.toString());
266
+ }
267
+ });
268
+ const queryString = searchParams.toString();
269
+ return queryString ? `${url}?${queryString}` : url;
270
+ }
271
+ };
@@ -149,3 +149,118 @@ describe('getEntityStatus', ()=>{
149
149
  });
150
150
  });
151
151
  });
152
+ describe('getResolvedImageUrl', ()=>{
153
+ describe('URL parsing and domain replacement', ()=>{
154
+ test('replaces downloads.ctfassets.net with images.ctfassets.net', ()=>{
155
+ const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.ctfassets.net/space/asset.jpg');
156
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg');
157
+ });
158
+ test('handles protocol-relative URLs', ()=>{
159
+ const result = (0, _entityHelpers.getResolvedImageUrl)('//downloads.ctfassets.net/space/asset.jpg');
160
+ expect(result).toBe('//images.ctfassets.net/space/asset.jpg');
161
+ });
162
+ test('does not modify URLs that are not from downloads.ctfassets.net', ()=>{
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.ctfassets.net URLs', ()=>{
167
+ const result = (0, _entityHelpers.getResolvedImageUrl)('https://images.ctfassets.net/space/asset.jpg');
168
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg');
169
+ });
170
+ });
171
+ describe('query parameters', ()=>{
172
+ test('adds width parameter', ()=>{
173
+ const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.ctfassets.net/space/asset.jpg', {
174
+ width: 100
175
+ });
176
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg?width=100');
177
+ });
178
+ test('adds height parameter', ()=>{
179
+ const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.ctfassets.net/space/asset.jpg', {
180
+ height: 200
181
+ });
182
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg?height=200');
183
+ });
184
+ test('adds fit parameter', ()=>{
185
+ const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.ctfassets.net/space/asset.jpg', {
186
+ fit: 'thumb'
187
+ });
188
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg?fit=thumb');
189
+ });
190
+ test('adds multiple parameters', ()=>{
191
+ const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.ctfassets.net/space/asset.jpg', {
192
+ width: 100,
193
+ height: 200,
194
+ fit: 'thumb'
195
+ });
196
+ expect(result).toContain('width=100');
197
+ expect(result).toContain('height=200');
198
+ expect(result).toContain('fit=thumb');
199
+ expect(result).toContain('images.ctfassets.net');
200
+ });
201
+ test('skips undefined parameters', ()=>{
202
+ const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.ctfassets.net/space/asset.jpg', {
203
+ width: 100,
204
+ height: undefined,
205
+ fit: 'thumb'
206
+ });
207
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg?width=100&fit=thumb');
208
+ });
209
+ test('returns URL without query string when no params provided', ()=>{
210
+ const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.ctfassets.net/space/asset.jpg');
211
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg');
212
+ });
213
+ test('returns URL without query string when all params are undefined', ()=>{
214
+ const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.ctfassets.net/space/asset.jpg', {
215
+ width: undefined,
216
+ height: undefined,
217
+ fit: undefined
218
+ });
219
+ expect(result).toBe('https://images.ctfassets.net/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
+ width: 100,
230
+ height: 200
231
+ });
232
+ expect(result).toBe('/assets/image.jpg?width=100&height=200');
233
+ });
234
+ test('handles relative URLs with undefined params', ()=>{
235
+ const result = (0, _entityHelpers.getResolvedImageUrl)('/assets/image.jpg', {
236
+ width: 100,
237
+ height: undefined
238
+ });
239
+ expect(result).toBe('/assets/image.jpg?width=100');
240
+ });
241
+ test('returns relative URL unchanged when all params are undefined', ()=>{
242
+ const result = (0, _entityHelpers.getResolvedImageUrl)('/assets/image.jpg', {
243
+ width: undefined,
244
+ height: 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.ctfassets.net/space/asset.jpg?foo=bar', {
252
+ width: 100
253
+ });
254
+ expect(result).toContain('foo=bar');
255
+ expect(result).toContain('width=100');
256
+ });
257
+ test('handles URLs with fragments', ()=>{
258
+ const result = (0, _entityHelpers.getResolvedImageUrl)('https://downloads.ctfassets.net/space/asset.jpg#section', {
259
+ width: 100
260
+ });
261
+ expect(result).toContain('images.ctfassets.net');
262
+ expect(result).toContain('width=100');
263
+ expect(result).toContain('#section');
264
+ });
265
+ });
266
+ });
@@ -189,3 +189,33 @@ export const getEntryImage = async ({ entry, contentType, localeCode }, getAsset
189
189
  return null;
190
190
  }
191
191
  };
192
+ const DOWNLOADS_ENDPOINT = 'downloads.ctfassets.net';
193
+ const TRANSFORMATIONS_ENDPOINT = 'images.ctfassets.net';
194
+ export const getResolvedImageUrl = (url, params)=>{
195
+ try {
196
+ const urlToParse = url.startsWith('//') ? `https:${url}` : url;
197
+ const parsedUrl = new URL(urlToParse);
198
+ if (parsedUrl.hostname === DOWNLOADS_ENDPOINT) {
199
+ parsedUrl.hostname = TRANSFORMATIONS_ENDPOINT;
200
+ }
201
+ if (params) {
202
+ Object.entries(params).forEach(([key, value])=>{
203
+ if (value !== undefined) {
204
+ parsedUrl.searchParams.set(key, value.toString());
205
+ }
206
+ });
207
+ }
208
+ const result = parsedUrl.toString();
209
+ return url.startsWith('//') ? result.replace(/^https:/, '') : result;
210
+ } catch {
211
+ if (!params) return url;
212
+ const searchParams = new URLSearchParams();
213
+ Object.entries(params).forEach(([key, value])=>{
214
+ if (value !== undefined) {
215
+ searchParams.set(key, value.toString());
216
+ }
217
+ });
218
+ const queryString = searchParams.toString();
219
+ return queryString ? `${url}?${queryString}` : url;
220
+ }
221
+ };
@@ -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,118 @@ describe('getEntityStatus', ()=>{
145
145
  });
146
146
  });
147
147
  });
148
+ describe('getResolvedImageUrl', ()=>{
149
+ describe('URL parsing and domain replacement', ()=>{
150
+ test('replaces downloads.ctfassets.net with images.ctfassets.net', ()=>{
151
+ const result = getResolvedImageUrl('https://downloads.ctfassets.net/space/asset.jpg');
152
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg');
153
+ });
154
+ test('handles protocol-relative URLs', ()=>{
155
+ const result = getResolvedImageUrl('//downloads.ctfassets.net/space/asset.jpg');
156
+ expect(result).toBe('//images.ctfassets.net/space/asset.jpg');
157
+ });
158
+ test('does not modify URLs that are not from downloads.ctfassets.net', ()=>{
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.ctfassets.net URLs', ()=>{
163
+ const result = getResolvedImageUrl('https://images.ctfassets.net/space/asset.jpg');
164
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg');
165
+ });
166
+ });
167
+ describe('query parameters', ()=>{
168
+ test('adds width parameter', ()=>{
169
+ const result = getResolvedImageUrl('https://downloads.ctfassets.net/space/asset.jpg', {
170
+ width: 100
171
+ });
172
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg?width=100');
173
+ });
174
+ test('adds height parameter', ()=>{
175
+ const result = getResolvedImageUrl('https://downloads.ctfassets.net/space/asset.jpg', {
176
+ height: 200
177
+ });
178
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg?height=200');
179
+ });
180
+ test('adds fit parameter', ()=>{
181
+ const result = getResolvedImageUrl('https://downloads.ctfassets.net/space/asset.jpg', {
182
+ fit: 'thumb'
183
+ });
184
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg?fit=thumb');
185
+ });
186
+ test('adds multiple parameters', ()=>{
187
+ const result = getResolvedImageUrl('https://downloads.ctfassets.net/space/asset.jpg', {
188
+ width: 100,
189
+ height: 200,
190
+ fit: 'thumb'
191
+ });
192
+ expect(result).toContain('width=100');
193
+ expect(result).toContain('height=200');
194
+ expect(result).toContain('fit=thumb');
195
+ expect(result).toContain('images.ctfassets.net');
196
+ });
197
+ test('skips undefined parameters', ()=>{
198
+ const result = getResolvedImageUrl('https://downloads.ctfassets.net/space/asset.jpg', {
199
+ width: 100,
200
+ height: undefined,
201
+ fit: 'thumb'
202
+ });
203
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg?width=100&fit=thumb');
204
+ });
205
+ test('returns URL without query string when no params provided', ()=>{
206
+ const result = getResolvedImageUrl('https://downloads.ctfassets.net/space/asset.jpg');
207
+ expect(result).toBe('https://images.ctfassets.net/space/asset.jpg');
208
+ });
209
+ test('returns URL without query string when all params are undefined', ()=>{
210
+ const result = getResolvedImageUrl('https://downloads.ctfassets.net/space/asset.jpg', {
211
+ width: undefined,
212
+ height: undefined,
213
+ fit: undefined
214
+ });
215
+ expect(result).toBe('https://images.ctfassets.net/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
+ width: 100,
226
+ height: 200
227
+ });
228
+ expect(result).toBe('/assets/image.jpg?width=100&height=200');
229
+ });
230
+ test('handles relative URLs with undefined params', ()=>{
231
+ const result = getResolvedImageUrl('/assets/image.jpg', {
232
+ width: 100,
233
+ height: undefined
234
+ });
235
+ expect(result).toBe('/assets/image.jpg?width=100');
236
+ });
237
+ test('returns relative URL unchanged when all params are undefined', ()=>{
238
+ const result = getResolvedImageUrl('/assets/image.jpg', {
239
+ width: undefined,
240
+ height: 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.ctfassets.net/space/asset.jpg?foo=bar', {
248
+ width: 100
249
+ });
250
+ expect(result).toContain('foo=bar');
251
+ expect(result).toContain('width=100');
252
+ });
253
+ test('handles URLs with fragments', ()=>{
254
+ const result = getResolvedImageUrl('https://downloads.ctfassets.net/space/asset.jpg#section', {
255
+ width: 100
256
+ });
257
+ expect(result).toContain('images.ctfassets.net');
258
+ expect(result).toContain('width=100');
259
+ expect(result).toContain('#section');
260
+ });
261
+ });
262
+ });
@@ -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
+ width?: number;
86
+ height?: 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-alpha.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": "aae7ea3dcdc2010b1bc8c15a4d6ce3ddba9702e2"
64
64
  }