From 967dc1edda831c7204a112c85bc6c4d13afba734 Mon Sep 17 00:00:00 2001 From: sc-illiakovalenko Date: Wed, 22 Jul 2020 11:23:03 +0300 Subject: [PATCH 1/5] Update mediaApi functions --- packages/sitecore-jss/src/mediaApi.test.ts | 73 ++++++++++++++++++++++ packages/sitecore-jss/src/mediaApi.ts | 15 +++-- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/packages/sitecore-jss/src/mediaApi.test.ts b/packages/sitecore-jss/src/mediaApi.test.ts index 2c3d5c42e7..2aef7fc1e1 100644 --- a/packages/sitecore-jss/src/mediaApi.test.ts +++ b/packages/sitecore-jss/src/mediaApi.test.ts @@ -59,6 +59,40 @@ describe('updateImageUrl', () => { expect(url.pathname).to.startsWith('/~/jssmedia/'); }); + describe('should replace url using custom mediaUrlPrefix', () => { + it('should replace /-assets/ with /-/jssmedia', () => { + const original = 'http://sitecore/-assets/lorem/ipsum.jpg'; + const mediaUrlPrefix = /\/([-~]{1})assets\//i; + const updated = updateImageUrl(original, undefined, mediaUrlPrefix); + const url = URL(updated); + expect(url.pathname).to.startsWith('/-/jssmedia/'); + }) + + it('should replace /~assets/ with /~/jssmedia', () => { + const original = 'http://sitecore/~assets/lorem/ipsum.jpg'; + const mediaUrlPrefix = /\/([-~]{1})assets\//i; + const updated = updateImageUrl(original, undefined, mediaUrlPrefix); + const url = URL(updated); + expect(url.pathname).to.startsWith('/~/jssmedia/'); + }) + + it('should replace /-/assets/ with /-/jssmedia/', () => { + const original = 'http://sitecore/-/assets/lorem/ipsum.jpg'; + const mediaUrlPrefix = /\/([-~]{1})\/assets\//i; + const updated = updateImageUrl(original, undefined, mediaUrlPrefix); + const url = URL(updated); + expect(url.pathname).to.startsWith('/-/jssmedia/'); + }) + + it('should replace /~/assets/ with /~/jssmedia/', () => { + const original = 'http://sitecore/~/assets/lorem/ipsum.jpg'; + const mediaUrlPrefix = /\/([-~]{1})\/assets\//i; + const updated = updateImageUrl(original, undefined, mediaUrlPrefix); + const url = URL(updated); + expect(url.pathname).to.startsWith('/~/jssmedia/'); + }) + }); + it('should merge querystring and params', () => { const src = '/media/lorem/ipsum.jpg?x=valueX'; const params = { y: 'valueY', z: 'valueZ' }; @@ -98,4 +132,43 @@ describe('getSrcSet', () => { const srcSet = getSrcSet(original, params, { as: '1', w: '9999' }); expect(srcSet).to.equal(expected); }); + + describe('should replace url using custom mediaUrlPrefix', () => { + const params = [ + { w: '1000' }, + { w: '500' }, + ]; + + it('should replace /-assets/ with /-/jssmedia', () => { + const original = '/-assets/lorem/ipsum.jpg'; + const expected = '/-/jssmedia/lorem/ipsum.jpg?w=1000 1000w, /-/jssmedia/lorem/ipsum.jpg?w=500 500w'; + const mediaUrlPrefix = /\/([-~]{1})assets\//i; + const srcSet = getSrcSet(original, params, undefined, mediaUrlPrefix); + expect(srcSet).to.equal(expected); + }) + + it('should replace /~assets/ with /~/jssmedia', () => { + const original = '/~assets/lorem/ipsum.jpg'; + const expected = '/~/jssmedia/lorem/ipsum.jpg?w=1000 1000w, /~/jssmedia/lorem/ipsum.jpg?w=500 500w'; + const mediaUrlPrefix = /\/([-~]{1})assets\//i; + const srcSet = getSrcSet(original, params, undefined, mediaUrlPrefix); + expect(srcSet).to.equal(expected); + }) + + it('should replace /-/assets/ with /-/jssmedia/', () => { + const original = '/-/assets/lorem/ipsum.jpg'; + const expected = '/-/jssmedia/lorem/ipsum.jpg?w=1000 1000w, /-/jssmedia/lorem/ipsum.jpg?w=500 500w'; + const mediaUrlPrefix = /\/([-~]{1})\/assets\//i; + const srcSet = getSrcSet(original, params, undefined, mediaUrlPrefix); + expect(srcSet).to.equal(expected); + }) + + it('should replace /~/assets/ with /~/jssmedia/', () => { + const original = '/~/assets/lorem/ipsum.jpg'; + const expected = '/~/jssmedia/lorem/ipsum.jpg?w=1000 1000w, /~/jssmedia/lorem/ipsum.jpg?w=500 500w'; + const mediaUrlPrefix = /\/([-~]{1})\/assets\//i; + const srcSet = getSrcSet(original, params, undefined, mediaUrlPrefix); + expect(srcSet).to.equal(expected); + }) + }); }); diff --git a/packages/sitecore-jss/src/mediaApi.ts b/packages/sitecore-jss/src/mediaApi.ts index afd698b4b5..47e885a759 100644 --- a/packages/sitecore-jss/src/mediaApi.ts +++ b/packages/sitecore-jss/src/mediaApi.ts @@ -40,7 +40,11 @@ export const findEditorImageTag = (editorMarkup: string) => { * This replacement allows the JSS media handler to be used for JSS app assets. * Also, any provided `params` are used as the querystring parameters for the media URL. */ -export const updateImageUrl = (url: string, params?: { [key: string]: string | undefined }) => { +export const updateImageUrl = ( + url: string, + params?: { [key: string]: string | undefined }, + mediaUrlPrefix: RegExp = mediaUrlPrefixRegex +) => { // polyfill node `global` in browser to workaround https://github.com/unshiftio/url-parse/issues/150 if (typeof window !== 'undefined' && !(window as any).global) { (window as any).global = {}; @@ -49,10 +53,10 @@ export const updateImageUrl = (url: string, params?: { [key: string]: string | u parsed.set('query', { ...parsed.query, ...params }); - const match = mediaUrlPrefixRegex.exec(parsed.pathname); + const match = mediaUrlPrefix.exec(parsed.pathname); if (match && match.length > 1) { // regex will provide us with /-/ or /~/ type - parsed.set('pathname', parsed.pathname.replace(mediaUrlPrefixRegex, `/${match[1]}/jssmedia/`)); + parsed.set('pathname', parsed.pathname.replace(mediaUrlPrefix, `/${match[1]}/jssmedia/`)); } return parsed.toString(); @@ -72,7 +76,8 @@ export const updateImageUrl = (url: string, params?: { [key: string]: string | u export const getSrcSet = ( url: string, srcSet: Array<{ [key: string]: string | undefined }>, - imageParams?: { [key: string]: string | undefined } + imageParams?: { [key: string]: string | undefined }, + mediaUrlPrefix?: RegExp ) => { return srcSet .map((params) => { @@ -81,7 +86,7 @@ export const getSrcSet = ( if (!imageWidth) { return null; } - return `${updateImageUrl(url, newParams)} ${imageWidth}w`; + return `${updateImageUrl(url, newParams, mediaUrlPrefix)} ${imageWidth}w`; }) .filter((value) => value) .join(', '); From 09462a81cf15a9847914528893b76224d758e21f Mon Sep 17 00:00:00 2001 From: sc-illiakovalenko Date: Wed, 22 Jul 2020 12:52:19 +0300 Subject: [PATCH 2/5] [sitecore-jss-react] Provide mediaUrlPrefix property --- .../src/components/Image.test.tsx | 80 +++++++++++++++++++ .../src/components/Image.tsx | 22 +++-- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/packages/sitecore-jss-react/src/components/Image.test.tsx b/packages/sitecore-jss-react/src/components/Image.test.tsx index 7ad7d4caa1..f5e725fa64 100644 --- a/packages/sitecore-jss-react/src/components/Image.test.tsx +++ b/packages/sitecore-jss-react/src/components/Image.test.tsx @@ -171,6 +171,86 @@ describe('', () => { }); }); + describe('with "mediaUrlPrefix" property', () => { + it('should transform url with "value" property value', () => { + const props = { + media: { value: { src: '/~assets/img/test0.png', alt: 'my image' } }, + id: 'some-id', + style: { width: '100%' }, + className: 'the-dude-abides', + mediaUrlPrefix: /\/([-~]{1})assets\//i + }; + const rendered = mount(); + + expect(rendered.find('img').prop('src')).to.equal('/~/jssmedia/img/test0.png'); + + rendered.setProps({ + ...props, + media: { value: { src: '/-assets/img/test0.png', alt: 'my image' } }, + }); + + expect(rendered.find('img').prop('src')).to.equal('/-/jssmedia/img/test0.png'); + }); + + it('should transform url with direct image object, no value/editable', () => { + const props = { + media: { + src: '/~assets/img/test0.png', + width: 8, + height: 10, + }, + id: 'some-id', + style: { + width: '100%', + }, + className: 'the-dude-abides', + mediaUrlPrefix: /\/([-~]{1})assets\//i + }; + const rendered = mount(); + + expect(rendered.find('img').prop('src')).to.equal('/~/jssmedia/img/test0.png'); + + rendered.setProps({ + ...props, + media: { + src: '/-assets/img/test0.png', + width: 8, + height: 10, + } + }); + + expect(rendered.find('img').prop('src')).to.equal('/-/jssmedia/img/test0.png'); + }); + + it('should transform url with responsive image object', () => { + const props = { + media: { + src: '/~assets/img/test0.png', + }, + srcSet: [{ mw: 100 }, { mw: 300 }], + sizes: '(min-width: 960px) 300px, 100px', + id: 'some-id', + className: 'the-dude-abides', + mediaUrlPrefix: /\/([-~]{1})assets\//i + }; + + const rendered = mount(); + + expect(rendered.find('img').prop('src')).to.equal('/~/jssmedia/img/test0.png'); + + rendered.setProps({ + ...props, + media: { + src: '/-assets/img/test0.png', + width: 8, + height: 10, + } + }); + + expect(rendered.find('img').prop('src')).to.equal('/-/jssmedia/img/test0.png'); + }); + }); + it('should render no when media prop is empty', () => { const img: any = ''; const rendered = mount(); diff --git a/packages/sitecore-jss-react/src/components/Image.tsx b/packages/sitecore-jss-react/src/components/Image.tsx index b6c0c530c3..d151c82d5b 100644 --- a/packages/sitecore-jss-react/src/components/Image.tsx +++ b/packages/sitecore-jss-react/src/components/Image.tsx @@ -58,6 +58,15 @@ export interface ImageProps { srcSet?: Array; + /** + * Custom regexp that finds media URL prefix that will be replaced by `/-/jssmedia` or `/~/jssmedia`. + * @example + * /\/([-~]{1})assets\//i + * /-assets/website -> /-/jssmedia/website + * /~assets/website -> /~/jssmedia/website + */ + mediaUrlPrefix?: RegExp; + /** HTML attributes that will be appended to the rendered tag. */ [attributeName: string]: any; } @@ -82,7 +91,8 @@ const getImageAttrs = ( srcSet: any; otherAttrs: any[]; }, - imageParams: any + imageParams: any, + mediaUrlPrefix?: RegExp ) => { if (!src) { return null; @@ -93,10 +103,10 @@ const getImageAttrs = ( }; // update image URL for jss handler and image rendering params - const resolvedSrc = mediaApi.updateImageUrl(src, imageParams); + const resolvedSrc = mediaApi.updateImageUrl(src, imageParams, mediaUrlPrefix); if (srcSet) { // replace with HTML-formatted srcset, including updated image URLs - newAttrs.srcSet = mediaApi.getSrcSet(resolvedSrc, srcSet, imageParams); + newAttrs.srcSet = mediaApi.getSrcSet(resolvedSrc, srcSet, imageParams, mediaUrlPrefix); } // always output original src as fallback for older browsers newAttrs.src = resolvedSrc; @@ -108,6 +118,7 @@ export const Image: React.SFC = ({ editable, imageParams, field, + mediaUrlPrefix, ...otherProps }) => { // allows the mistake of using 'field' prop instead of 'media' (consistent with other helpers) @@ -131,7 +142,7 @@ export const Image: React.SFC = ({ const foundImgProps = convertAttributesToReactProps(foundImg.attrs); // Note: otherProps may override values from foundImgProps, e.g. `style`, `className` prop // We do not attempt to merge. - const imgAttrs = getImageAttrs({ ...foundImgProps, ...otherProps } as any, imageParams); + const imgAttrs = getImageAttrs({ ...foundImgProps, ...otherProps } as any, imageParams, mediaUrlPrefix); if (!imgAttrs) { return getEditableWrapper(dynamicMedia.editable); } @@ -147,7 +158,7 @@ export const Image: React.SFC = ({ return null; } - const attrs = getImageAttrs({ ...img, ...otherProps }, imageParams); + const attrs = getImageAttrs({ ...img, ...otherProps }, imageParams, mediaUrlPrefix); if (attrs) { return ; } @@ -166,6 +177,7 @@ Image.propTypes = { }), ]), editable: PropTypes.bool, + mediaUrlPrefix: PropTypes.instanceOf(RegExp), imageParams: PropTypes.objectOf(PropTypes.oneOfType([ PropTypes.number.isRequired, PropTypes.string.isRequired From 2ba15fc2bc842f36d40b0bf45d935382d78f5ebc Mon Sep 17 00:00:00 2001 From: sc-illiakovalenko Date: Wed, 22 Jul 2020 14:42:25 +0300 Subject: [PATCH 3/5] [sitecore-jss-angular] provide mediaUrlPrefix --- .../src/components/image.directive.spec.ts | 102 +++++++++++++++++- .../src/components/image.directive.ts | 14 ++- 2 files changed, 113 insertions(+), 3 deletions(-) diff --git a/packages/sitecore-jss-angular/src/components/image.directive.spec.ts b/packages/sitecore-jss-angular/src/components/image.directive.spec.ts index 6eecde19cc..4ae1ef9ffb 100644 --- a/packages/sitecore-jss-angular/src/components/image.directive.spec.ts +++ b/packages/sitecore-jss-angular/src/components/image.directive.spec.ts @@ -21,7 +21,7 @@ class TestComponent { @Component({ selector: 'test-image2', template: ` - + `, }) class AnotherTestComponent { @@ -29,6 +29,7 @@ class AnotherTestComponent { @Input() editable = true; @Input() params: any = {}; @Input() imageAttrs: any = { }; + @Input() mediaUrlPrefix?: RegExp; } describe('', () => { @@ -215,6 +216,105 @@ describe('', () => { expect(url.query.w).toBe(imageParams.w); expect(url.query.hash).toEqual('B973470AA333773341C62A76511361C88897E2D4'); }); + + it('should update image url using custom mediaUrlPrefix', () => { + const testImg = (expectedPrefix: string) => { + const img = de.nativeElement.getElementsByTagName('img')[0]; + const url = URL(img.getAttribute('src'), null as any, true); + + expect(url.pathname).toContain(expectedPrefix); + expect(url.query.h).toBe(imageParams.h); + expect(url.query.w).toBe(imageParams.w); + expect(url.query.hash).toBeUndefined(); + }; + + comp2.mediaUrlPrefix = /\/([-~]{1})test\//i; + comp2.field = { + value: { + src: '/-test/assets/img/test0.png', + alt: 'my image', + height: '650', + width: '300', + }, + }; + + fixture2.detectChanges(); + + testImg('/-/jssmedia/'); + + comp2.field = { + value: { + src: '/~test/assets/img/test0.png', + alt: 'my image', + height: '650', + width: '300', + }, + }; + + fixture2.detectChanges(); + + testImg('/~/jssmedia/'); + + comp2.field = { + value: { + src: '/-invalid/assets/img/test0.png', + alt: 'my image', + height: '650', + width: '300', + }, + }; + + fixture2.detectChanges(); + + testImg('/-invalid/'); + }); + + it('should update image url using custom mediaUrlPrefix with srcSet', () => { + const testImg = (expectedPrefix: string) => { + const img = de.nativeElement.getElementsByTagName('img')[0]; + const url = img.getAttribute('srcset'); + + expect(url).toBe(`${expectedPrefix}assets/img/test0.png?h=100&w=150&mw=100 150w, ${expectedPrefix}assets/img/test0.png?h=100&w=150&mw=300 150w`); + }; + + comp2.imageAttrs = { + srcSet: [{ mw: 100 }, { mw: 300 }], + }; + comp2.mediaUrlPrefix = /\/([-~]{1})test\//i; + + comp2.field = { + value: { + src: '/-test/assets/img/test0.png', + alt: 'my image', + }, + }; + + fixture2.detectChanges(); + + testImg('/-/jssmedia/'); + + comp2.field = { + value: { + src: '/~test/assets/img/test0.png', + alt: 'my image', + }, + }; + + fixture2.detectChanges(); + + testImg('/~/jssmedia/'); + + comp2.field = { + value: { + src: '/~invalid/assets/img/test0.png', + alt: 'my image', + }, + }; + + fixture2.detectChanges(); + + testImg('/~invalid/'); + }); }); describe('with "editable" property value but editing disabled', () => { diff --git a/packages/sitecore-jss-angular/src/components/image.directive.ts b/packages/sitecore-jss-angular/src/components/image.directive.ts index 7511bb0af0..79d090f346 100644 --- a/packages/sitecore-jss-angular/src/components/image.directive.ts +++ b/packages/sitecore-jss-angular/src/components/image.directive.ts @@ -12,6 +12,16 @@ export class ImageDirective implements OnChanges { // tslint:disable-next-line:no-input-rename @Input('scImageEditable') editable = true; + /** + * Custom regexp that finds media URL prefix that will be replaced by `/-/jssmedia` or `/~/jssmedia`. + * @example + * /\/([-~]{1})assets\//i + * /-assets/website -> /-/jssmedia/website + * /~assets/website -> /~/jssmedia/website + */ + // tslint:disable-next-line:no-input-rename + @Input('scImageMediaUrlPrefix') mediaUrlPrefix?: RegExp; + // tslint:disable-next-line:no-input-rename @Input('scImageUrlParams') urlParams = {}; @@ -94,10 +104,10 @@ export class ImageDirective implements OnChanges { ...otherAttrs, }; // update image URL for jss handler and image rendering params - src = mediaApi.updateImageUrl(src, imageParams); + src = mediaApi.updateImageUrl(src, imageParams, this.mediaUrlPrefix); if (srcSet) { // replace with HTML-formatted srcset, including updated image URLs - newAttrs.srcSet = mediaApi.getSrcSet(src, srcSet, imageParams); + newAttrs.srcSet = mediaApi.getSrcSet(src, srcSet, imageParams, this.mediaUrlPrefix); } else { newAttrs.src = src; } From 48a0725bb16800c96431678a6f5d05de4a647e76 Mon Sep 17 00:00:00 2001 From: sc-illiakovalenko Date: Wed, 22 Jul 2020 17:31:49 +0300 Subject: [PATCH 4/5] [sitecore-jss-vue] Provide mediaUrlPrefix --- .../src/components/Image.test.ts | 205 +++++++++++++++++- .../sitecore-jss-vue/src/components/Image.ts | 23 +- 2 files changed, 219 insertions(+), 9 deletions(-) diff --git a/packages/sitecore-jss-vue/src/components/Image.test.ts b/packages/sitecore-jss-vue/src/components/Image.test.ts index 3f5e8e177e..ca363984c7 100644 --- a/packages/sitecore-jss-vue/src/components/Image.test.ts +++ b/packages/sitecore-jss-vue/src/components/Image.test.ts @@ -83,13 +83,212 @@ describe('', () => { it('should render img with additional props', () => { expect(imgAttrs).toMatchObject(attrs); }); + }); + + describe('update image url', () => { + const attrs = { id: 'some-id', height: '100', width: '150' }; + + it('should handle /-/media', () => { + const props = { + media: { + value: { + src: '/-/media/img/test0.png', + alt: 'my image', + }, + }, + imageParams: { + h: '100', + w: '150', + }, + }; + + const rendered = mount(Image, { context: { props, attrs } }).find('img'); + const img = rendered.find('img'); + + const url = new URL(img.attributes().src, {}, true); + expect(url.pathname).toContain('/-/jssmedia/'); + expect(url.query.h).toBe(props.imageParams.h); + expect(url.query.w).toBe(props.imageParams.w); + }); + + it('should handle /~/media', () => { + const props = { + media: { + value: { + src: '/~/media/img/test0.png', + alt: 'my image', + }, + }, + imageParams: { + h: '100', + w: '150', + }, + }; + + const rendered = mount(Image, { context: { props, attrs } }).find('img'); + const img = rendered.find('img'); + + const url = new URL(img.attributes().src, {}, true); + expect(url.pathname).toContain('/~/jssmedia/'); + expect(url.query.h).toBe(props.imageParams.h); + expect(url.query.w).toBe(props.imageParams.w); + }); + + it('should handle custom mediaUrlPrefix, /-assets', () => { + const props = { + media: { + value: { + src: '/-assets/img/test0.png', + alt: 'my image', + }, + }, + imageParams: { + h: '100', + w: '150', + }, + mediaUrlPrefix: /\/([-~]{1})assets\//i, + }; + + const rendered = mount(Image, { context: { props, attrs } }).find('img'); + const img = rendered.find('img'); + + const url = new URL(img.attributes().src, {}, true); + expect(url.pathname).toContain('/-/jssmedia/'); + expect(url.query.h).toBe(props.imageParams.h); + expect(url.query.w).toBe(props.imageParams.w); + }); + + it('should handle custom mediaUrlPrefix, /~assets', () => { + const props = { + media: { + value: { + src: '/~assets/img/test0.png', + alt: 'my image', + }, + }, + imageParams: { + h: '100', + w: '150', + }, + mediaUrlPrefix: /\/([-~]{1})assets\//i, + }; + + const rendered = mount(Image, { context: { props, attrs } }).find('img'); + const img = rendered.find('img'); - it('should update image url', () => { const url = new URL(img.attributes().src, {}, true); - expect(url.pathname.indexOf('/-/jssmedia/')).toBeGreaterThan(-1); + expect(url.pathname).toContain('/~/jssmedia/'); expect(url.query.h).toBe(props.imageParams.h); expect(url.query.w).toBe(props.imageParams.w); - expect(url.query.hash).toBe('B973470AA333773341C62A76511361C88897E2D4') + }); + }); + + describe('update image srcSet', () => { + const attrs = { + srcSet: [{ mw: 100 }, { mw: 300 }], + }; + + it('should handle /-/media', () => { + const props = { + media: { + value: { + src: '/-/media/img/test0.png', + alt: 'my image', + }, + }, + imageParams: { + h: '100', + w: '150', + } + }; + + const rendered = mount(Image, { context: { props, attrs } }).find('img'); + const img = rendered.find('img'); + + expect(img.attributes().srcset).toBe('/-/jssmedia/img/test0.png?h=100&w=150&mw=100 150w, /-/jssmedia/img/test0.png?h=100&w=150&mw=300 150w'); + }); + + it('should handle /~/media', () => { + const props = { + media: { + value: { + src: '/~/media/img/test0.png', + alt: 'my image', + }, + }, + imageParams: { + h: '100', + w: '150', + }, + }; + + const rendered = mount(Image, { context: { props, attrs } }).find('img'); + const img = rendered.find('img'); + + expect(img.attributes().srcset).toBe('/~/jssmedia/img/test0.png?h=100&w=150&mw=100 150w, /~/jssmedia/img/test0.png?h=100&w=150&mw=300 150w'); + }); + + it('should handle custom mediaUrlPrefix, /-assets', () => { + const props = { + media: { + value: { + src: '/-assets/img/test0.png', + alt: 'my image', + }, + }, + imageParams: { + h: '100', + w: '150', + }, + mediaUrlPrefix: /\/([-~]{1})assets\//i, + }; + + const rendered = mount(Image, { context: { props, attrs } }).find('img'); + const img = rendered.find('img'); + + expect(img.attributes().srcset).toBe('/-/jssmedia/img/test0.png?h=100&w=150&mw=100 150w, /-/jssmedia/img/test0.png?h=100&w=150&mw=300 150w'); + }); + + it('should handle custom mediaUrlPrefix, /~assets', () => { + const props = { + media: { + value: { + src: '/~assets/img/test0.png', + alt: 'my image', + }, + }, + imageParams: { + h: '100', + w: '150', + }, + mediaUrlPrefix: /\/([-~]{1})assets\//i, + }; + + const rendered = mount(Image, { context: { props, attrs } }).find('img'); + const img = rendered.find('img'); + + expect(img.attributes().srcset).toBe('/~/jssmedia/img/test0.png?h=100&w=150&mw=100 150w, /~/jssmedia/img/test0.png?h=100&w=150&mw=300 150w'); + }); + + it('should handle custom mediaUrlPrefix, invalid prefix', () => { + const props = { + media: { + value: { + src: '/~invalid/img/test0.png', + alt: 'my image', + }, + }, + imageParams: { + h: '100', + w: '150', + }, + mediaUrlPrefix: /\/([-~]{1})assets\//i, + }; + + const rendered = mount(Image, { context: { props, attrs } }).find('img'); + const img = rendered.find('img'); + + expect(img.attributes().srcset).toBe('/~invalid/img/test0.png?h=100&w=150&mw=100 150w, /~invalid/img/test0.png?h=100&w=150&mw=300 150w'); }); }); diff --git a/packages/sitecore-jss-vue/src/components/Image.ts b/packages/sitecore-jss-vue/src/components/Image.ts index 371cf58a7b..0643a52f39 100644 --- a/packages/sitecore-jss-vue/src/components/Image.ts +++ b/packages/sitecore-jss-vue/src/components/Image.ts @@ -25,6 +25,15 @@ export interface ImageProps { */ editable?: boolean; + /** + * Custom regexp that finds media URL prefix that will be replaced by `/-/jssmedia` or `/~/jssmedia`. + * @example + * /\/([-~]{1})assets\//i + * /-assets/website -> /-/jssmedia/website + * /~assets/website -> /~/jssmedia/website + */ + mediaUrlPrefix?: RegExp; + /** * Parameters that will be attached to Sitecore media URLs */ @@ -51,7 +60,8 @@ const getImageAttrs = ( srcSet?: any; otherAttrs?: any; }, - imageParams: any + imageParams: any, + mediaUrlPrefix?: RegExp ) => { if (!src) { return null; @@ -61,10 +71,10 @@ const getImageAttrs = ( }; // update image URL for jss handler and image rendering params - const resolvedSrc = mediaApi.updateImageUrl(src, imageParams); + const resolvedSrc = mediaApi.updateImageUrl(src, imageParams, mediaUrlPrefix); if (srcSet) { // replace with HTML-formatted srcset, including updated image URLs - newAttrs.srcSet = mediaApi.getSrcSet(resolvedSrc, srcSet, imageParams); + newAttrs.srcSet = mediaApi.getSrcSet(resolvedSrc, srcSet, imageParams, mediaUrlPrefix); } else { newAttrs.src = resolvedSrc; } @@ -79,12 +89,13 @@ export const Image: FunctionalComponentOptions = { media: { type: Object, required: true }, editable: { type: Boolean, default: true }, imageParams: { type: Object }, + mediaUrlPrefix: { type: RegExp }, }, // Need to assign `any` return type because Vue type definitions are inaccurate. // The Vue type definitions set `render` to a return type of VNode and that's it. // However, it is possible to return null | string | VNode[] | VNodeChildrenArrayContents. render(createElement: CreateElement, context: RenderContext): any { - const { media, editable, imageParams } = context.props; + const { media, editable, imageParams, mediaUrlPrefix } = context.props; const contextAttrs = context.data.attrs; if (!media || (!media.editable && !media.value && !media.src)) { @@ -98,7 +109,7 @@ export const Image: FunctionalComponentOptions = { return getEditableWrapper(media.editable, createElement); } - const imgAttrs = getImageAttrs({ ...foundImg.attrs, ...contextAttrs }, imageParams); + const imgAttrs = getImageAttrs({ ...foundImg.attrs, ...contextAttrs }, imageParams, mediaUrlPrefix); if (!imgAttrs) { return getEditableWrapper(media.editable, createElement); } @@ -114,7 +125,7 @@ export const Image: FunctionalComponentOptions = { return null; } - const attrs = getImageAttrs({ ...img, ...contextAttrs }, imageParams); + const attrs = getImageAttrs({ ...img, ...contextAttrs }, imageParams, mediaUrlPrefix); if (attrs) { // in functional components, context.data should be passed along to the // `createElement` function in order to retain attributes and events From cdcf758bb59d93f5ad152230d4243240d8291ca9 Mon Sep 17 00:00:00 2001 From: sc-illiakovalenko Date: Thu, 23 Jul 2020 10:03:33 +0300 Subject: [PATCH 5/5] Jsdoc --- packages/sitecore-jss/src/mediaApi.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sitecore-jss/src/mediaApi.ts b/packages/sitecore-jss/src/mediaApi.ts index 47e885a759..3958d46897 100644 --- a/packages/sitecore-jss/src/mediaApi.ts +++ b/packages/sitecore-jss/src/mediaApi.ts @@ -37,6 +37,7 @@ export const findEditorImageTag = (editorMarkup: string) => { /** * Receives a Sitecore media URL and replaces `/~/media` or `/-/media` with `/~/jssmedia` or `/-/jssmedia`, respectively. + * Can use `mediaUrlPrefix` in order to use custom checker. * This replacement allows the JSS media handler to be used for JSS app assets. * Also, any provided `params` are used as the querystring parameters for the media URL. */