From 191cd828d9a9d20f61e0483e0e48efa4caaa8d7e Mon Sep 17 00:00:00 2001 From: Eitan Peer Date: Tue, 27 Jun 2017 18:29:41 +0300 Subject: [PATCH 1/2] Add cld-responsive as an alias for responsive attribute --- src/cloudinary-image.component.spec.ts | 37 ++++++++++++++++++++++++++ src/cloudinary-image.component.ts | 4 ++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/cloudinary-image.component.spec.ts b/src/cloudinary-image.component.spec.ts index 9bdd2f1d..815c7ce9 100644 --- a/src/cloudinary-image.component.spec.ts +++ b/src/cloudinary-image.component.spec.ts @@ -33,6 +33,7 @@ describe('CloudinaryImage', () => { }).createComponent(TestComponent); fixture.detectChanges(); // initial binding + expect(localCloudinary.responsive).toHaveBeenCalled(); // all elements with an attached CloudinaryImage des = fixture.debugElement.query(By.directive(CloudinaryImage)); @@ -183,5 +184,41 @@ describe('CloudinaryImage', () => { expect(img.attributes.getNamedItem('data-src')).toBeNull(); }); }); + + describe('responsive images with nested transformations using the cld-responsive attribute', () => { + @Component({ + template: ` + + + + ` + }) + class TestComponent { } + + let fixture: ComponentFixture; + let des: DebugElement; // the elements w/ the directive + + beforeEach(() => { + fixture = TestBed.configureTestingModule({ + declarations: [CloudinaryTransformationDirective, CloudinaryImage, TestComponent], + providers: [{ provide: Cloudinary, useValue: localCloudinary }] + }).createComponent(TestComponent); + + fixture.detectChanges(); // initial binding + expect(localCloudinary.responsive).toHaveBeenCalled(); + + // all elements with an attached CloudinaryImage + des = fixture.debugElement.query(By.directive(CloudinaryImage)); + }); + + it('creates an img element which encodes the directive attributes to the URL', () => { + const img = des.children[0].nativeElement as HTMLImageElement; + expect(img.src).toEqual(jasmine.stringMatching + (/c_scale,l_text:roboto_25_bold:SDK,w_300\/e_art:hokusai\/f_auto\/responsive_sample.jpg/)); + console.log('img', img); + expect(img.attributes.getNamedItem('data-src').value).toEqual(jasmine.stringMatching( + /c_scale,l_text:roboto_25_bold:SDK,w_300\/e_art:hokusai\/f_auto\/responsive_sample.jpg/)); + }); + }); }); diff --git a/src/cloudinary-image.component.ts b/src/cloudinary-image.component.ts index d701ba3d..5aed3f6c 100644 --- a/src/cloudinary-image.component.ts +++ b/src/cloudinary-image.component.ts @@ -54,7 +54,9 @@ export class CloudinaryImage implements AfterViewInit, OnInit, OnDestroy { const nativeElement = this.el.nativeElement; const image = nativeElement.children[0]; const options = this.cloudinary.toCloudinaryAttributes(nativeElement.attributes, this.transformations); - + if ('cld_responsive' in options) { + options.responsive = true; + } const imageTag = this.cloudinary.imageTag(this.publicId, options); this.setElementAttributes(image, imageTag.attributes()); if (options.responsive) { From c8167cb1e2b3129d5add5c53c8c412fdc92ad695 Mon Sep 17 00:00:00 2001 From: Eitan Peer Date: Wed, 28 Jun 2017 00:23:09 +0300 Subject: [PATCH 2/2] Allow any attribute to be prefixed with cld Prefix can also include an optional dash or underscore. Prefix is removed by the SDK when using the attribute as an attribute to provide aliases to attributes that may be used by other directives --- src/cloudinary-image.component.ts | 3 --- src/cloudinary.service.spec.ts | 32 ++++++++++++++++++++----------- src/cloudinary.service.ts | 15 ++++++++------- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/cloudinary-image.component.ts b/src/cloudinary-image.component.ts index 5aed3f6c..2d564636 100644 --- a/src/cloudinary-image.component.ts +++ b/src/cloudinary-image.component.ts @@ -54,9 +54,6 @@ export class CloudinaryImage implements AfterViewInit, OnInit, OnDestroy { const nativeElement = this.el.nativeElement; const image = nativeElement.children[0]; const options = this.cloudinary.toCloudinaryAttributes(nativeElement.attributes, this.transformations); - if ('cld_responsive' in options) { - options.responsive = true; - } const imageTag = this.cloudinary.imageTag(this.publicId, options); this.setElementAttributes(image, imageTag.attributes()); if (options.responsive) { diff --git a/src/cloudinary.service.spec.ts b/src/cloudinary.service.spec.ts index cd49b568..67414b67 100644 --- a/src/cloudinary.service.spec.ts +++ b/src/cloudinary.service.spec.ts @@ -5,7 +5,7 @@ import { Cloudinary, isJsonLikeString, isNamedNodeMap, - transformKeyNamesFromKebabToSnakeCase + transformKeyNames } from './cloudinary.service'; import CloudinaryConfiguration from './cloudinary-configuration.class'; @@ -70,9 +70,9 @@ describe('Cloudinary service', () => { }); }); - describe('transformKeyNamesFromKebabToSnakeCase', () => { + describe('transformKeyNames', () => { it('Transforms property names of json-like strings from kebab-case to snake_case', () => { - expect(transformKeyNamesFromKebabToSnakeCase('{"aaa-aaa":"1", "bbb-bbb":"2", "cc": "ccc-ccc"}')).toEqual( + expect(transformKeyNames('{"aaa-aaa":"1", "bbb-bbb":"2", "cc": "ccc-ccc"}')).toEqual( { aaa_aaa: '1', bbb_bbb: '2', @@ -81,7 +81,7 @@ describe('Cloudinary service', () => { ); }); it('Transforms property names of json-like strings spanning multi-lines from kebab-case to snake_case', () => { - expect(transformKeyNamesFromKebabToSnakeCase(`{"aaa-aaa":"1", + expect(transformKeyNames(`{"aaa-aaa":"1", "bbb-bbb":"2", "cc": "ccc-ccc"}`)).toEqual( { @@ -92,7 +92,7 @@ describe('Cloudinary service', () => { ); }); it('Transforms property names of objects from kebab-case to snake_case', () => { - expect(transformKeyNamesFromKebabToSnakeCase({'aaa-aaa': 1, 'bbb-bbb': 2, cc: 'ccc-ccc'})).toEqual( + expect(transformKeyNames({'aaa-aaa': 1, 'bbb-bbb': 2, cc: 'ccc-ccc'})).toEqual( { aaa_aaa: 1, bbb_bbb: 2, @@ -100,14 +100,24 @@ describe('Cloudinary service', () => { } ); }); + it('Transforms property names by stripping cld prefix', () => { + // "cld" prefix can be followed by an optional dash or underscore + expect(transformKeyNames('{"cldResponsive":"1", "cld-width":"2", "cld_height": "ccc-ccc"}')).toEqual( + { + responsive: '1', + width: '2', + height: 'ccc-ccc' + } + ); + }); it('does not affect primitive values', () => { - expect(transformKeyNamesFromKebabToSnakeCase(123)).toEqual(123); - expect(transformKeyNamesFromKebabToSnakeCase(undefined)).toBeUndefined(); - expect(transformKeyNamesFromKebabToSnakeCase('')).toEqual(''); - expect(transformKeyNamesFromKebabToSnakeCase('a b c')).toEqual('a b c'); + expect(transformKeyNames(123)).toEqual(123); + expect(transformKeyNames(undefined)).toBeUndefined(); + expect(transformKeyNames('')).toEqual(''); + expect(transformKeyNames('a b c')).toEqual('a b c'); }); it('iterates over array elements to transform its members', () => { - expect(transformKeyNamesFromKebabToSnakeCase([{ + expect(transformKeyNames([{ 'aaa-aaa': 'aaa-aaa', 'bbb-bbb': 'bbb-bbb', 'ccc': 'ccc' @@ -125,7 +135,7 @@ describe('Cloudinary service', () => { ]); }); it('transforms complex json-like objects into options', () => { - expect(transformKeyNamesFromKebabToSnakeCase(`{"aaa-aaa":"1", + expect(transformKeyNames(`{"aaa-aaa":"1", "bbb-bbb":"2", "transform-ation": [{ "effect": "sepia", "fetch_format": "auto"}] }`)).toEqual( diff --git a/src/cloudinary.service.ts b/src/cloudinary.service.ts index 73692179..186bb35b 100644 --- a/src/cloudinary.service.ts +++ b/src/cloudinary.service.ts @@ -29,7 +29,7 @@ const namedNodeMapToObject = function (source: NamedNodeMap): any { return target; }; -const transformKeyNamesFromKebabToSnakeCase = function (obj: any): any { +const transformKeyNames = function (obj: any): any { let _obj = obj; if (isJsonLikeString(obj)) { // Given attribute value is in the form of a JSON object - @@ -42,13 +42,14 @@ const transformKeyNamesFromKebabToSnakeCase = function (obj: any): any { if (Array.isArray(_obj)) { // Transform all the array values (e.g. transformation array) _obj = _obj.map(currentValue => { - return transformKeyNamesFromKebabToSnakeCase(currentValue); + return transformKeyNames(currentValue); }); } else if (typeof _obj === 'object') { Object.keys(_obj).forEach(key => { // Replace the key name with the snake_case - const kebabKey = key.replace(/-/g, '_').toLocaleLowerCase(); - const kebabValue = transformKeyNamesFromKebabToSnakeCase(_obj[key]); + // Then strip cld prefix if it exists (with an optional dash or underscore) + const kebabKey = key.replace(/-/g, '_').toLocaleLowerCase().replace(/^cld(-|_)?/, ''); + const kebabValue = transformKeyNames(_obj[key]); delete _obj[key]; _obj[kebabKey] = kebabValue; }); @@ -104,7 +105,7 @@ export class Cloudinary { */ toCloudinaryAttributes(attributes: NamedNodeMap, childTransformations?: QueryList): any { - const options = transformKeyNamesFromKebabToSnakeCase(attributes); + const options = transformKeyNames(attributes); // Add chained transformations if (childTransformations && childTransformations.length > 0) { @@ -126,8 +127,8 @@ export class Cloudinary { /* Return a provider object that creates our configurable service */ export function provideCloudinary(cloudinaryJsLib: any, configuration: CloudinaryConfiguration) { - return { provide: Cloudinary, useFactory: () => new Cloudinary(cloudinaryJsLib, configuration) }; + return { provide: Cloudinary, useFactory: () => new Cloudinary(cloudinaryJsLib, configuration) }; }; // For unit tests -export { isJsonLikeString, isNamedNodeMap, transformKeyNamesFromKebabToSnakeCase, namedNodeMapToObject }; +export { isJsonLikeString, isNamedNodeMap, transformKeyNames, namedNodeMapToObject };