diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index 75d013078a..96bfb02028 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -601,9 +601,10 @@ "content-metadata": { "presets": { "default": { - "exif:exif": "*" + "exif:exif": "*" } - } + }, + "multi-value-pipe-separator" : ", " }, "sideNav": { "expandedSidenav": true, diff --git a/docs/README.md b/docs/README.md index 70328cf129..007acbe6fa 100644 --- a/docs/README.md +++ b/docs/README.md @@ -162,6 +162,7 @@ for more information about installing and using the source code. | [Text Highlight pipe](core/pipes/text-highlight.pipe.md) | Adds highlighting to words or sections of text that match a search string. | [Source](../lib/core/pipes/text-highlight.pipe.ts) | | [Time Ago pipe](core/pipes/time-ago.pipe.md) | Converts a recent past date into a number of days ago. | [Source](../lib/core/pipes/time-ago.pipe.ts) | | [User Initial pipe](core/pipes/user-initial.pipe.md) | Takes the name fields of a UserProcessModel object and extracts and formats the initials. | [Source](../lib/core/pipes/user-initial.pipe.ts) | +| [Multi value pipe](core/pipes/multi-value.pipe.md) | Takes a list of values to stringify them with a custom separator. | [Source](../lib/core/pipes/multi-value.pipe.ts) | ### Services diff --git a/docs/content-services/components/content-metadata-card.component.md b/docs/content-services/components/content-metadata-card.component.md index 2b73654d7c..773ed6b807 100644 --- a/docs/content-services/components/content-metadata-card.component.md +++ b/docs/content-services/components/content-metadata-card.component.md @@ -274,3 +274,21 @@ example below shows this with an aspect-oriented config: Nothing - since this aspect is not related to the node, it will simply be ignored and not displayed. The aspects to be displayed are calculated as an intersection of the preset's aspects and the aspects related to the node. + +## Multi value card properties +Multi value properties are displayed one after another separated by a comma. This card makes use of the [Multi Value Pipe](../../core/pipes/multi-value.pipe.ts). + +To customize the separator used by this card you can set it in your `app.config.json` inside your content-metadata configuration: + +```json +"content-metadata": { + "presets": { + "default": { + "includeAll": true, + "exclude": "exif:exif", + "exif:exif": [ "exif:pixelXDimension", "exif:pixelYDimension"] + } + }, + "multi-value-pipe-separator" : " - " +}, +``` diff --git a/docs/core/pipes/multi-value.pipe.md b/docs/core/pipes/multi-value.pipe.md new file mode 100644 index 0000000000..a0f948a9b6 --- /dev/null +++ b/docs/core/pipes/multi-value.pipe.md @@ -0,0 +1,39 @@ +# [Multi Value Pipe](../../../lib/core/pipes/multi-value.pipe.ts "Defined in multi-value.pipe.ts") + +Takes an array of strings and turns it into one string where items are separated by a separator. The default separator applied to the list is `', '`, however, you can set your own separator in the params of the pipe. + +## Basic Usage + + + +### Default separator + +```HTML +
+ List {{ values | multiValue }} +
+``` + +#### Result + +![multi-value-pipe](../../docassets/images/multi-value-default.pipe.png) + +### Custom separator + +```HTML +
+ List {{ values | multiValue: ' :) ' }} +
+``` + + + +#### Result + +![multi-value-pipe](../../docassets/images/multi-value.pipe.png) + +## Details + +The pipe gets every one of the items passed to the pipe and stringifies it adding the separator set in the configuration. + +You will need to specify the separator you want to use for it to work. diff --git a/docs/docassets/images/multi-value-default.pipe.png b/docs/docassets/images/multi-value-default.pipe.png new file mode 100644 index 0000000000..bd57f0221b Binary files /dev/null and b/docs/docassets/images/multi-value-default.pipe.png differ diff --git a/docs/docassets/images/multi-value.pipe.png b/docs/docassets/images/multi-value.pipe.png new file mode 100644 index 0000000000..3a72938e66 Binary files /dev/null and b/docs/docassets/images/multi-value.pipe.png differ diff --git a/e2e/core/viewer/file-extensions/viewer-arcive.component.e2e.ts b/e2e/core/viewer/file-extensions/viewer-arcive.component.e2e.ts index 744bce338a..2b23a1e19f 100644 --- a/e2e/core/viewer/file-extensions/viewer-arcive.component.e2e.ts +++ b/e2e/core/viewer/file-extensions/viewer-arcive.component.e2e.ts @@ -15,21 +15,21 @@ * limitations under the License. */ -import TestConfig = require('../../test.config'); +import TestConfig = require('../../../test.config'); import { LoginPage } from '@alfresco/adf-testing'; -import { ViewerPage } from '../../pages/adf/viewerPage'; -import { ContentServicesPage } from '../../pages/adf/contentServicesPage'; +import { ViewerPage } from '../../../pages/adf/viewerPage'; +import { ContentServicesPage } from '../../../pages/adf/contentServicesPage'; -import CONSTANTS = require('../../util/constants'); -import resources = require('../../util/resources'); +import CONSTANTS = require('../../../util/constants'); +import resources = require('../../../util/resources'); import { StringUtil } from '@alfresco/adf-testing'; -import { FolderModel } from '../../models/ACS/folderModel'; -import { AcsUserModel } from '../../models/ACS/acsUserModel'; +import { FolderModel } from '../../../models/ACS/folderModel'; +import { AcsUserModel } from '../../../models/ACS/acsUserModel'; import { AlfrescoApiCompatibility as AlfrescoApi } from '@alfresco/js-api'; -import { UploadActions } from '../../actions/ACS/upload.actions'; +import { UploadActions } from '../../../actions/ACS/upload.actions'; describe('Viewer', () => { diff --git a/e2e/core/viewer/file-extensions/viewer-component.e2e.ts b/e2e/core/viewer/file-extensions/viewer-component.e2e.ts index 59a140906c..49f0c71a67 100644 --- a/e2e/core/viewer/file-extensions/viewer-component.e2e.ts +++ b/e2e/core/viewer/file-extensions/viewer-component.e2e.ts @@ -33,7 +33,6 @@ import { AlfrescoApiCompatibility as AlfrescoApi } from '@alfresco/js-api'; import { UploadActions } from '../../../actions/ACS/upload.actions'; import { NavigationBarPage } from '../../..//pages/adf/navigationBarPage'; - describe('Viewer', () => { const viewerPage = new ViewerPage(); diff --git a/e2e/core/viewer/file-extensions/viewer-text.component.e2e.ts b/e2e/core/viewer/file-extensions/viewer-text.component.e2e.ts index 9db136c4f3..a98518b53e 100644 --- a/e2e/core/viewer/file-extensions/viewer-text.component.e2e.ts +++ b/e2e/core/viewer/file-extensions/viewer-text.component.e2e.ts @@ -104,6 +104,4 @@ describe('Viewer', () => { }); }); - - }); diff --git a/lib/content-services/content-metadata/services/property-groups-translator.service.ts b/lib/content-services/content-metadata/services/property-groups-translator.service.ts index 2833552aba..5ec97325fe 100644 --- a/lib/content-services/content-metadata/services/property-groups-translator.service.ts +++ b/lib/content-services/content-metadata/services/property-groups-translator.service.ts @@ -25,7 +25,9 @@ import { CardViewDatetimeItemModel, CardViewIntItemModel, CardViewFloatItemModel, - LogService + LogService, + MultiValuePipe, + AppConfigService } from '@alfresco/adf-core'; import { Property, CardViewGroup, OrganisedPropertyGroup } from '../interfaces/content-metadata.interfaces'; @@ -46,7 +48,12 @@ export class PropertyGroupTranslatorService { static readonly RECOGNISED_ECM_TYPES = [D_TEXT, D_MLTEXT, D_DATE, D_DATETIME, D_INT, D_LONG, D_FLOAT, D_DOUBLE, D_BOOLEAN]; - constructor(private logService: LogService) { + valueSeparator: string; + + constructor(private logService: LogService, + private multiValuePipe: MultiValuePipe, + private appConfig: AppConfigService) { + this.valueSeparator = this.appConfig.get('content-metadata.multi-value-pipe-separator'); } public translateToCardViewGroups(propertyGroups: OrganisedPropertyGroup[], propertyValues): CardViewGroup[] { @@ -115,7 +122,9 @@ export class PropertyGroupTranslatorService { case D_TEXT: default: cardViewItemProperty = new CardViewTextItemModel(Object.assign(propertyDefinition, { - multiline: false + multivalued: property.multiValued, + multiline: property.multiValued, + pipes: [{ pipe: this.multiValuePipe, params: [this.valueSeparator]}] })); } diff --git a/lib/core/app-config/schema.json b/lib/core/app-config/schema.json index ecfe2b9969..bf6809bf16 100644 --- a/lib/core/app-config/schema.json +++ b/lib/core/app-config/schema.json @@ -859,6 +859,10 @@ ] } } + }, + "multi-value-pipe-separator": { + "description": "Content metadata's separator for multi value properties", + "type": "string" } } }, diff --git a/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.ts b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.ts index 6a44787326..deeabaf1b7 100644 --- a/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.ts +++ b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.ts @@ -18,6 +18,7 @@ import { Component, Input, OnChanges, ViewChild } from '@angular/core'; import { CardViewTextItemModel } from '../../models/card-view-textitem.model'; import { CardViewUpdateService } from '../../services/card-view-update.service'; +import { AppConfigService } from '../../../app-config/app-config.service'; @Component({ selector: 'adf-card-view-textitem', @@ -25,6 +26,9 @@ import { CardViewUpdateService } from '../../services/card-view-update.service'; styleUrls: ['./card-view-textitem.component.scss'] }) export class CardViewTextItemComponent implements OnChanges { + + static DEFAULT_SEPARATOR = ', '; + @Input() property: CardViewTextItemModel; @@ -40,12 +44,15 @@ export class CardViewTextItemComponent implements OnChanges { inEdit: boolean = false; editedValue: string; errorMessages: string[]; + valueSeparator: string; - constructor(private cardViewUpdateService: CardViewUpdateService) { + constructor(private cardViewUpdateService: CardViewUpdateService, + private appConfig: AppConfigService) { + this.valueSeparator = this.appConfig.get('content-metadata.multi-value-pipe-separator') || CardViewTextItemComponent.DEFAULT_SEPARATOR; } ngOnChanges(): void { - this.editedValue = this.property.value; + this.editedValue = this.property.multiline ? this.property.displayValue : this.property.value; } showProperty(): boolean { @@ -78,20 +85,29 @@ export class CardViewTextItemComponent implements OnChanges { } reset(): void { - this.editedValue = this.property.value; + this.editedValue = this.property.multiline ? this.property.displayValue : this.property.value; this.setEditMode(false); } update(): void { if (this.property.isValid(this.editedValue)) { - this.cardViewUpdateService.update(this.property, this.editedValue); - this.property.value = this.editedValue; + const updatedValue = this.prepareValueForUpload(this.property, this.editedValue); + this.cardViewUpdateService.update(this.property, updatedValue); + this.property.value = updatedValue; this.setEditMode(false); } else { this.errorMessages = this.property.getValidationErrors(this.editedValue); } } + prepareValueForUpload(property: CardViewTextItemModel, value: string): string | string [] { + const listOfValues = value; + if (property.multivalued) { + return listOfValues.split(this.valueSeparator); + } + return listOfValues; + } + onTextAreaInputChange() { this.errorMessages = this.property.getValidationErrors(this.editedValue); } diff --git a/lib/core/card-view/interfaces/card-view-textitem-properties.interface.ts b/lib/core/card-view/interfaces/card-view-textitem-properties.interface.ts index d80fa18d1e..511271e8d3 100644 --- a/lib/core/card-view/interfaces/card-view-textitem-properties.interface.ts +++ b/lib/core/card-view/interfaces/card-view-textitem-properties.interface.ts @@ -20,6 +20,7 @@ import { CardViewTextItemPipeProperty } from './card-view-textitem-pipe-property export interface CardViewTextItemProperties extends CardViewItemProperties { multiline?: boolean; + multivalued?: boolean; pipes?: CardViewTextItemPipeProperty[]; clickCallBack?: any; } diff --git a/lib/core/card-view/models/card-view-textitem.model.ts b/lib/core/card-view/models/card-view-textitem.model.ts index 32a6acabb0..22de7d0b23 100644 --- a/lib/core/card-view/models/card-view-textitem.model.ts +++ b/lib/core/card-view/models/card-view-textitem.model.ts @@ -23,12 +23,14 @@ import { CardViewTextItemPipeProperty, CardViewTextItemProperties } from '../int export class CardViewTextItemModel extends CardViewBaseItemModel implements CardViewItem, DynamicComponentModel { type: string = 'text'; multiline?: boolean; + multivalued?: boolean; pipes?: CardViewTextItemPipeProperty[]; clickCallBack?: any; constructor(cardViewTextItemProperties: CardViewTextItemProperties) { super(cardViewTextItemProperties); this.multiline = !!cardViewTextItemProperties.multiline; + this.multivalued = !!cardViewTextItemProperties.multivalued; this.pipes = cardViewTextItemProperties.pipes || []; this.clickCallBack = cardViewTextItemProperties.clickCallBack ? cardViewTextItemProperties.clickCallBack : null; } diff --git a/lib/core/pipes/multi-value.pipe.spec.ts b/lib/core/pipes/multi-value.pipe.spec.ts new file mode 100644 index 0000000000..acc3ce103c --- /dev/null +++ b/lib/core/pipes/multi-value.pipe.spec.ts @@ -0,0 +1,66 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MultiValuePipe } from './multi-value.pipe'; +import { TestBed } from '@angular/core/testing'; +import { setupTestBed } from 'core'; +import { CoreTestingModule } from 'core/testing/core.testing.module'; + +describe('FullNamePipe', () => { + + let pipe: MultiValuePipe; + + setupTestBed({ + imports: [CoreTestingModule] + }); + + beforeEach(() => { + pipe = TestBed.get(MultiValuePipe); + + }); + + it('should add the separator when a list is provided', () => { + const values = ['cat', 'house', 'dog']; + expect(pipe.transform(values)).toBe('cat, house, dog'); + }); + + it('should add custom separator when set', () => { + const values = ['cat', 'house', 'dog']; + const customSeparator = ' - '; + expect(pipe.transform(values, customSeparator)).toBe('cat - house - dog'); + }); + + it('should not add separator when the list has only one item', () => { + const values = ['cat']; + expect(pipe.transform(values)).toBe('cat'); + }); + + it('should return empty string when an empty list is passed', () => { + const values = []; + expect(pipe.transform(values)).toBe(''); + }); + + it('should return empty string when an empty string is passed', () => { + const values = ''; + expect(pipe.transform(values)).toBe(''); + }); + + it('should return same string when the value passed is a string', () => { + const values = 'cat'; + expect(pipe.transform(values)).toBe('cat'); + }); +}); diff --git a/lib/core/pipes/multi-value.pipe.ts b/lib/core/pipes/multi-value.pipe.ts new file mode 100644 index 0000000000..a75a2959f0 --- /dev/null +++ b/lib/core/pipes/multi-value.pipe.ts @@ -0,0 +1,34 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'multiValue' }) +export class MultiValuePipe implements PipeTransform { + + static DEFAULT_SEPARATOR = ', '; + + transform(values: string | string [], valueSeparator: string = MultiValuePipe.DEFAULT_SEPARATOR): string { + + if (values && values instanceof Array) { + values.map((value) => value.trim()); + return values.join(valueSeparator); + } + + return values; + } +} diff --git a/lib/core/pipes/pipe.module.ts b/lib/core/pipes/pipe.module.ts index 2bd70806eb..8d1311671f 100644 --- a/lib/core/pipes/pipe.module.ts +++ b/lib/core/pipes/pipe.module.ts @@ -27,6 +27,7 @@ import { InitialUsernamePipe } from './user-initial.pipe'; import { FullNamePipe } from './full-name.pipe'; import { FormatSpacePipe } from './format-space.pipe'; import { FileTypePipe } from './file-type.pipe'; +import { MultiValuePipe } from './multi-value.pipe'; @NgModule({ imports: [ @@ -41,7 +42,8 @@ import { FileTypePipe } from './file-type.pipe'; FullNamePipe, NodeNameTooltipPipe, FormatSpacePipe, - FileTypePipe + FileTypePipe, + MultiValuePipe ], providers: [ FileSizePipe, @@ -51,7 +53,8 @@ import { FileTypePipe } from './file-type.pipe'; InitialUsernamePipe, NodeNameTooltipPipe, FormatSpacePipe, - FileTypePipe + FileTypePipe, + MultiValuePipe ], exports: [ FileSizePipe, @@ -62,7 +65,8 @@ import { FileTypePipe } from './file-type.pipe'; FullNamePipe, NodeNameTooltipPipe, FormatSpacePipe, - FileTypePipe + FileTypePipe, + MultiValuePipe ] }) export class PipeModule { diff --git a/lib/core/pipes/public-api.ts b/lib/core/pipes/public-api.ts index a40350bacf..9746057417 100644 --- a/lib/core/pipes/public-api.ts +++ b/lib/core/pipes/public-api.ts @@ -22,5 +22,6 @@ export * from './text-highlight.pipe'; export * from './time-ago.pipe'; export * from './user-initial.pipe'; export * from './full-name.pipe'; +export * from './multi-value.pipe'; export * from './pipe.module';