diff --git a/demo-shell/resources/i18n/en.json b/demo-shell/resources/i18n/en.json index 684535ce1b..248c851d2c 100644 --- a/demo-shell/resources/i18n/en.json +++ b/demo-shell/resources/i18n/en.json @@ -135,6 +135,7 @@ "DELETE": "Delete", "FAVORITES": "Add to favorites", "SHARE": "Share", + "SHARE_EDIT": "Edit settings", "THEME": "Select a theme", "SHOW_VERSION": "Show version", "HIDE_VERSION": "Hide version", diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index e00e9a7d66..4f8059f60e 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -2,6 +2,7 @@ "$schema": "../../lib/core/app-config/schema.json", "ecmHost": "http://{hostname}:{port}", "bpmHost": "http://{hostname}:{port}", + "baseShareUrl": null, "loginRoute": "login", "providers": "ALL", "contextRootBpm": "activiti-app", diff --git a/demo-shell/src/app/components/files/files.component.html b/demo-shell/src/app/components/files/files.component.html index ee956014b8..c578ee85f1 100644 --- a/demo-shell/src/app/components/files/files.component.html +++ b/demo-shell/src/app/components/files/files.component.html @@ -119,10 +119,13 @@ + ` +}) +class NodeShareTestComponent { + baseShareUrl = 'some-url/'; + documentList = { + selection: [] + }; +} + +describe('NodeSharedDirective', () => { + let fixture: ComponentFixture; + let component: NodeShareTestComponent; + let document: any; + let shareButtonElement; + let selection; + + setupTestBed({ + imports: [ + NoopAnimationsModule, + CoreModule.forRoot(), + ContentTestingModule, + ContentNodeShareModule + ], + declarations: [ + NodeShareTestComponent + ], + providers: [ + SharedLinksApiService + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NodeShareTestComponent); + document = TestBed.get(DOCUMENT); + component = fixture.componentInstance; + + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.destroy(); + }); + + beforeEach(() => { + shareButtonElement = fixture.debugElement.query(By.css('button')).nativeElement; + selection = { + entry: { + isFile: true, + properties: {} + } + }; + }); + + it('should have share button disabled when selection is empty', async() => { + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(shareButtonElement.disabled).toBe(true); + }); + }); + + it('should have button enabled when selection is not empty', () => { + component.documentList.selection = [selection]; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(shareButtonElement.disabled).toBe(false); + }); + }); + + it('should have button disabled when selection is not a file', () => { + selection.entry.isFile = false; + component.documentList.selection = [selection]; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(shareButtonElement.disabled).toBe(true); + }); + }); + + it('should indicate if file is not shared', () => { + component.documentList.selection = [selection]; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(shareButtonElement.title).toBe('Not Shared'); + }); + }); + + it('should indicate if file is already shared', () => { + selection.entry.properties['qshare:sharedId'] = 'someId'; + component.documentList.selection = [selection]; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(shareButtonElement.title).toBe('Shared'); + }); + }); + + it('should open share dialog on click event', () => { + selection.entry.properties['qshare:sharedId'] = 'someId'; + component.documentList.selection = [selection]; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + shareButtonElement.click(); + fixture.detectChanges(); + + expect(document.querySelector('.adf-share-link-dialog')).not.toBe(null); + }); + }); +}); diff --git a/lib/content-services/directives/node-share.directive.ts b/lib/content-services/content-node-share/content-node-share.directive.ts similarity index 65% rename from lib/content-services/directives/node-share.directive.ts rename to lib/content-services/content-node-share/content-node-share.directive.ts index cf9da2196e..6772d33236 100644 --- a/lib/content-services/directives/node-share.directive.ts +++ b/lib/content-services/content-node-share/content-node-share.directive.ts @@ -15,60 +15,57 @@ * limitations under the License. */ -import { Directive, Input, HostListener, ElementRef, OnChanges } from '@angular/core'; +import { Directive, Input, HostListener, OnChanges, NgZone } from '@angular/core'; import { MatDialog } from '@angular/material'; import { MinimalNodeEntity } from 'alfresco-js-api'; -import { ShareDialogComponent } from '../dialogs/share.dialog'; +import { ShareDialogComponent } from './content-node-share.dialog'; @Directive({ - selector: '[adf-share]' + selector: '[adf-share]', + exportAs: 'adfShare' }) export class NodeSharedDirective implements OnChanges { + isFile: boolean = false; + isShared: boolean = false; + /** Node to share. */ // tslint:disable-next-line:no-input-rename @Input('adf-share') node: MinimalNodeEntity; - /** Prefix to add to the generated link. */ @Input() baseShareUrl: string; @HostListener('click') onClick() { - this.shareNode(this.node); + if (this.node) { + this.shareNode(this.node); + } } - constructor(private dialog: MatDialog, - private elementRef: ElementRef) { - } + constructor(private dialog: MatDialog, private zone: NgZone) {} shareNode(node: MinimalNodeEntity) { if (node && node.entry && node.entry.isFile) { this.dialog.open(ShareDialogComponent, { width: '600px', - disableClose: true, + panelClass: 'adf-share-link-dialog', data: { node: node, baseShareUrl: this.baseShareUrl } }); - } else { - this.setDisableAttribute(true); } } ngOnChanges() { - if (!this.node || this.node.entry.isFolder) { - this.setDisableAttribute(true); - } else { - this.setDisableAttribute(false); - } + this.zone.onStable.subscribe(() => { + if (this.node) { + this.isFile = this.node.entry.isFile; + this.isShared = this.node.entry.properties['qshare:sharedId']; + } + }); } - - private setDisableAttribute(disable: boolean) { - this.elementRef.nativeElement.disabled = disable; - } - } diff --git a/lib/content-services/content-node-share/content-node-share.module.ts b/lib/content-services/content-node-share/content-node-share.module.ts new file mode 100644 index 0000000000..84aaf143bc --- /dev/null +++ b/lib/content-services/content-node-share/content-node-share.module.ts @@ -0,0 +1,55 @@ +/*! + * @license + * Copyright 2016 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 { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { CoreModule } from '@alfresco/adf-core'; +import { MaterialModule } from '../material.module'; +import { ShareDialogComponent } from './content-node-share.dialog'; +import { NodeSharedDirective } from './content-node-share.directive'; + +@NgModule({ + imports: [ + CoreModule.forChild(), + CommonModule, + MaterialModule + ], + declarations: [ + ShareDialogComponent, + NodeSharedDirective + ], + exports: [ + ShareDialogComponent, + NodeSharedDirective + ], + entryComponents: [ + ShareDialogComponent + ] +}) +export class ContentNodeShareModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: ContentNodeShareModule + }; + } + + static forChild(): ModuleWithProviders { + return { + ngModule: ContentNodeShareModule + }; + } +} diff --git a/lib/content-services/content-node-share/index.ts b/lib/content-services/content-node-share/index.ts new file mode 100644 index 0000000000..4c6ac1d58f --- /dev/null +++ b/lib/content-services/content-node-share/index.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright 2016 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. + */ + +export * from './public-api'; diff --git a/lib/content-services/content-node-share/public-api.ts b/lib/content-services/content-node-share/public-api.ts new file mode 100644 index 0000000000..221309fb02 --- /dev/null +++ b/lib/content-services/content-node-share/public-api.ts @@ -0,0 +1,21 @@ +/*! + * @license + * Copyright 2016 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. + */ + +export * from './content-node-share.dialog'; +export * from './content-node-share.directive'; + +export * from './content-node-share.module'; diff --git a/lib/content-services/content.module.ts b/lib/content-services/content.module.ts index f827a9c00a..10e0494944 100644 --- a/lib/content-services/content.module.ts +++ b/lib/content-services/content.module.ts @@ -32,6 +32,7 @@ import { SitesDropdownModule } from './site-dropdown/sites-dropdown.module'; import { BreadcrumbModule } from './breadcrumb/breadcrumb.module'; import { VersionManagerModule } from './version-manager/version-manager.module'; import { ContentNodeSelectorModule } from './content-node-selector/content-node-selector.module'; +import { ContentNodeShareModule } from './content-node-share/content-node-share.module'; import { ContentDirectiveModule } from './directives/content-directive.module'; import { DialogModule } from './dialogs/dialog.module'; import { FolderDirectiveModule } from './folder-directive/folder-directive.module'; @@ -96,6 +97,7 @@ export function providers() { SitesDropdownModule, BreadcrumbModule, ContentNodeSelectorModule, + ContentNodeShareModule, ContentMetadataModule, FolderDirectiveModule, ContentDirectiveModule, @@ -112,6 +114,7 @@ export function providers() { SitesDropdownModule, BreadcrumbModule, ContentNodeSelectorModule, + ContentNodeShareModule, ContentMetadataModule, DialogModule, FolderDirectiveModule, @@ -139,6 +142,7 @@ export class ContentModuleLazy {} SitesDropdownModule, BreadcrumbModule, ContentNodeSelectorModule, + ContentNodeShareModule, ContentMetadataModule, FolderDirectiveModule, ContentDirectiveModule, @@ -166,6 +170,7 @@ export class ContentModuleLazy {} SitesDropdownModule, BreadcrumbModule, ContentNodeSelectorModule, + ContentNodeShareModule, ContentMetadataModule, DialogModule, FolderDirectiveModule, diff --git a/lib/content-services/dialogs/dialog.module.ts b/lib/content-services/dialogs/dialog.module.ts index 5d752b22c7..e0ce6fd724 100644 --- a/lib/content-services/dialogs/dialog.module.ts +++ b/lib/content-services/dialogs/dialog.module.ts @@ -24,7 +24,6 @@ import { MaterialModule } from '../material.module'; import { DownloadZipDialogComponent } from './download-zip.dialog'; import { FolderDialogComponent } from './folder.dialog'; import { NodeLockDialogComponent } from './node-lock.dialog'; -import { ShareDialogComponent } from './share.dialog'; import { ConfirmDialogComponent } from './confirm.dialog'; import { MatDatetimepickerModule } from '@mat-datetimepicker/core'; import { MatMomentDatetimeModule } from '@mat-datetimepicker/moment'; @@ -43,21 +42,18 @@ import { MatMomentDatetimeModule } from '@mat-datetimepicker/moment'; DownloadZipDialogComponent, FolderDialogComponent, NodeLockDialogComponent, - ShareDialogComponent, ConfirmDialogComponent ], exports: [ DownloadZipDialogComponent, FolderDialogComponent, NodeLockDialogComponent, - ShareDialogComponent, ConfirmDialogComponent ], entryComponents: [ DownloadZipDialogComponent, FolderDialogComponent, NodeLockDialogComponent, - ShareDialogComponent, ConfirmDialogComponent ] }) diff --git a/lib/content-services/dialogs/public-api.ts b/lib/content-services/dialogs/public-api.ts index dab370de9b..a44bcf2e5a 100644 --- a/lib/content-services/dialogs/public-api.ts +++ b/lib/content-services/dialogs/public-api.ts @@ -18,7 +18,6 @@ export * from './download-zip.dialog'; export * from './folder.dialog'; export * from './node-lock.dialog'; -export * from './share.dialog'; export * from './confirm.dialog'; export * from './dialog.module'; diff --git a/lib/content-services/dialogs/share.dialog.html b/lib/content-services/dialogs/share.dialog.html deleted file mode 100644 index c1e9cde367..0000000000 --- a/lib/content-services/dialogs/share.dialog.html +++ /dev/null @@ -1,32 +0,0 @@ -

{{ 'SHARE.TITLE' | translate }} {{fileName}}

- - - - - - {{ 'SHARE.ACTIONS.SHARE' | translate }} - - - - -

{{ 'SHARE.DESCRIPTION' | translate }}

-
{{ 'SHARE.ALERT' | translate }}
- -
- - - link - -
- -

{{ 'SHARE.UNSHARED' | translate }}

- -
- - - - diff --git a/lib/content-services/dialogs/share.dialog.scss b/lib/content-services/dialogs/share.dialog.scss deleted file mode 100644 index 4a2bde0edf..0000000000 --- a/lib/content-services/dialogs/share.dialog.scss +++ /dev/null @@ -1,14 +0,0 @@ -.spacer { flex: 1 1 auto; } - -.adf-share-dialog .mat-dialog-actions .mat-button-wrapper { - text-transform: uppercase; -} - -.full-width { - width: 100%; -} - -.adf-share-link { - margin-left: 10px; - font-size: 18px !important; -} diff --git a/lib/content-services/dialogs/share.dialog.spec.ts b/lib/content-services/dialogs/share.dialog.spec.ts deleted file mode 100644 index 6a42ed2488..0000000000 --- a/lib/content-services/dialogs/share.dialog.spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -/*! - * @license - * Copyright 2016 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 { async, TestBed } from '@angular/core/testing'; -import { ComponentFixture } from '@angular/core/testing'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; -import { of } from 'rxjs'; -import { ShareDialogComponent } from './share.dialog'; -import { ContentTestingModule } from '../testing/content.testing.module'; -import { SharedLinksApiService, setupTestBed } from '@alfresco/adf-core'; - -describe('ShareDialogComponent', () => { - - let fixture: ComponentFixture; - let spyCreate: any; - let spyDelete: any; - let component: ShareDialogComponent; - let sharedLinksApiService: SharedLinksApiService; - const dialogRef = { - close: jasmine.createSpy('close') - }; - - let data: any = { - node: { entry: { properties: { 'qshare:sharedId': 'example-link' }, name: 'example-name' } }, - baseShareUrl: 'baseShareUrl-example' - }; - - setupTestBed({ - imports: [ContentTestingModule], - providers: [ - { provide: MatDialogRef, useValue: dialogRef }, - { provide: MAT_DIALOG_DATA, useValue: data } - ] - }); - - beforeEach(() => { - fixture = TestBed.createComponent(ShareDialogComponent); - sharedLinksApiService = TestBed.get(SharedLinksApiService); - component = fixture.componentInstance; - - fixture.detectChanges(); - - spyCreate = spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(of({ entry: { id: 'test-sharedId' } })); - spyDelete = spyOn(sharedLinksApiService, 'deleteSharedLink').and.returnValue(of({})); - }); - - it('should init the dialog with the file name and baseShareUrl', async(() => { - component.data = { - baseShareUrl: 'base-url/', - node: { entry: { properties: { 'qshare:sharedId': 'example-link' }, name: 'example-name' } } - }; - - component.ngOnInit(); - - fixture.detectChanges(); - - fixture.whenStable().then(() => { - expect(fixture.nativeElement.querySelector('#adf-share-link').value).toBe('base-url/example-link'); - }); - })); - - describe('public link creation', () => { - - it('should not create the public link if it already present in the node', () => { - component.data = { - node: { entry: { properties: { 'qshare:sharedId': 'example-link' }, name: 'example-name' } } - }; - - component.ngOnInit(); - - expect(spyCreate).not.toHaveBeenCalled(); - }); - - it('should not create the public link if is not present in the node', () => { - component.data = { - node: { entry: { properties: {}, name: 'example-name' } }, - baseShareUrl: 'baseShareUrl-example' - }; - - component.ngOnInit(); - - expect(spyCreate).not.toHaveBeenCalled(); - }); - - it('should be able to delete the shared link', async(() => { - component.data = { - node: { entry: { name: 'example-name', properties: { 'qshare:sharedId': 'example-link' } } }, - baseShareUrl: 'baseShareUrl-example' - }; - component.ngOnInit(); - fixture.detectChanges(); - fixture.nativeElement.querySelector('#adf-share-toggle-input').click(); - fixture.detectChanges(); - fixture.whenStable().then(() => { - expect(spyDelete).toHaveBeenCalled(); - expect(component.data.node.entry.properties['qshare:sharedId']).toBeNull(); - }); - })); - - it('should show the toggle disabled when the shareid property is null', async(() => { - component.data = { - node: { entry: { name: 'example-name', properties: { 'qshare:sharedId': null } } }, - baseShareUrl: 'baseShareUrl-example' - }; - component.ngOnInit(); - fixture.detectChanges(); - fixture.whenStable().then(() => { - expect(fixture.nativeElement.querySelector('#adf-share-toggle')).not.toBeNull(); - expect(fixture.nativeElement.querySelector('#adf-share-toggle.mat-checked')).toBeNull(); - }); - })); - }); - -}); diff --git a/lib/content-services/dialogs/share.dialog.ts b/lib/content-services/dialogs/share.dialog.ts deleted file mode 100644 index 9bba190191..0000000000 --- a/lib/content-services/dialogs/share.dialog.ts +++ /dev/null @@ -1,100 +0,0 @@ -/*! - * @license - * Copyright 2016 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 { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; -import { SharedLinksApiService } from '@alfresco/adf-core'; -import { SharedLinkEntry } from 'alfresco-js-api'; - -@Component({ - selector: 'adf-share-dialog', - templateUrl: './share.dialog.html', - styleUrls: ['./share.dialog.scss'], - host: { 'class': 'adf-share-dialog' }, - encapsulation: ViewEncapsulation.None -}) -export class ShareDialogComponent implements OnInit { - - sharedId: string; - - fileName: string; - baseShareUrl: string; - - isFileShared: boolean = false; - isDisabled: boolean = false; - - constructor(private sharedLinksApiService: SharedLinksApiService, - private dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) - public data: any) { - } - - ngOnInit() { - if (this.data.node && this.data.node.entry) { - this.fileName = this.data.node.entry.name; - this.baseShareUrl = this.data.baseShareUrl; - - if (this.data.node.entry.properties && this.data.node.entry.properties['qshare:sharedId']) { - this.sharedId = this.data.node.entry.properties['qshare:sharedId']; - this.isFileShared = true; - } else { - this.isFileShared = false; - this.isDisabled = false; - } - } - } - - cancelShare() { - this.dialogRef.close(false); - } - - onSlideShareChange(event: any) { - this.isDisabled = true; - if (event.checked) { - this.createSharedLinks(this.data.node.entry.id); - } else { - this.deleteSharedLink(this.sharedId); - } - } - - createSharedLinks(nodeId: string) { - this.sharedLinksApiService.createSharedLinks(nodeId).subscribe((sharedLink: SharedLinkEntry) => { - if (sharedLink.entry) { - this.sharedId = sharedLink.entry.id; - this.data.node.entry.properties['qshare:sharedId'] = this.sharedId; - this.isFileShared = true; - this.isDisabled = false; - } - }, - () => { - this.isFileShared = false; - this.isDisabled = false; - }); - } - - deleteSharedLink(sharedId: string) { - this.sharedLinksApiService.deleteSharedLink(sharedId).subscribe(() => { - this.data.node.entry.properties['qshare:sharedId'] = null; - this.isFileShared = false; - this.isDisabled = false; - }, - () => { - this.isFileShared = true; - this.isDisabled = false; - }); - } -} diff --git a/lib/content-services/directives/content-directive.module.ts b/lib/content-services/directives/content-directive.module.ts index c21ec8b345..1a51d261d9 100644 --- a/lib/content-services/directives/content-directive.module.ts +++ b/lib/content-services/directives/content-directive.module.ts @@ -20,7 +20,6 @@ import { NgModule } from '@angular/core'; import { MaterialModule } from '../material.module'; import { NodeDownloadDirective } from './node-download.directive'; -import { NodeSharedDirective } from './node-share.directive'; import { NodeLockDirective } from './node-lock.directive'; @NgModule({ @@ -30,12 +29,10 @@ import { NodeLockDirective } from './node-lock.directive'; ], declarations: [ NodeDownloadDirective, - NodeSharedDirective, NodeLockDirective ], exports: [ NodeDownloadDirective, - NodeSharedDirective, NodeLockDirective ] }) diff --git a/lib/content-services/directives/node-share.directive.spec.ts b/lib/content-services/directives/node-share.directive.spec.ts deleted file mode 100644 index 0b67ed4649..0000000000 --- a/lib/content-services/directives/node-share.directive.spec.ts +++ /dev/null @@ -1,117 +0,0 @@ -/*! - * @license - * Copyright 2016 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 { Component, DebugElement } from '@angular/core'; -import { fakeAsync, async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; -import { NodeSharedDirective } from './node-share.directive'; -import { DialogModule } from '../dialogs/dialog.module'; -import { MatDialog } from '@angular/material'; - -@Component({ - template: ` -
-
` -}) -class TestComponent { - node = null; - - done = jasmine.createSpy('done'); -} - -describe('NodeSharedDirective', () => { - let fixture: ComponentFixture; - let element: DebugElement; - let component: TestComponent; - let dialog: MatDialog; - let dialogSpy; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - DialogModule - ], - declarations: [ - TestComponent, - NodeSharedDirective - ] - }).compileComponents() - .then(() => { - fixture = TestBed.createComponent(TestComponent); - component = fixture.componentInstance; - dialog = TestBed.get(MatDialog); - element = fixture.debugElement.query(By.directive(NodeSharedDirective)); - dialogSpy = spyOn(dialog, 'open'); - }); - })); - - describe('Share', () => { - - it('should not share when selection has no nodes', () => { - component.node = null; - - fixture.detectChanges(); - element.triggerEventHandler('click', null); - - expect(dialogSpy).not.toHaveBeenCalled(); - }); - - it('should not share when the selection node is a folder', () => { - component.node = { - entry: { - isFolder: true - } - }; - - fixture.detectChanges(); - element.triggerEventHandler('click', null); - - expect(dialogSpy).not.toHaveBeenCalled(); - }); - - it('should share when the selection node is a file', () => { - component.node = { - entry: { - isFolder: false, - isFile: true - } - }; - - fixture.detectChanges(); - element.triggerEventHandler('click', null); - - expect(dialogSpy).toHaveBeenCalled(); - }); - - it('should disable the button if no node is selected', fakeAsync(() => { - component.node = null; - - fixture.detectChanges(); - - expect(element.nativeElement.disabled).toEqual(true); - })); - - it('should enable the button if nodes is selected and is a file', fakeAsync(() => { - component.node = { entry: { id: '1', name: 'name1', isFolder: false, isFile: true } }; - - fixture.detectChanges(); - - expect(element.nativeElement.disabled).toEqual(false); - })); - - }); -}); diff --git a/lib/content-services/directives/public-api.ts b/lib/content-services/directives/public-api.ts index bc108d59f3..1df33c4d8b 100644 --- a/lib/content-services/directives/public-api.ts +++ b/lib/content-services/directives/public-api.ts @@ -16,6 +16,5 @@ */ export * from './node-download.directive'; -export * from './node-share.directive'; export * from './content-directive.module'; diff --git a/lib/content-services/i18n/en.json b/lib/content-services/i18n/en.json index 932e5ccee8..601068026f 100644 --- a/lib/content-services/i18n/en.json +++ b/lib/content-services/i18n/en.json @@ -254,15 +254,16 @@ } }, "SHARE": { - "TITLE": "Share", - "PUBLIC-LINK": "Public link", - "UNSHARED": "File not shared", - "DESCRIPTION": "This is a link to your file", - "ALERT": "Anyone can access this link", - "ACTIONS": { - "SHARE": "SHARE", - "CLOSE": "CLOSE", - "COPY-LINK": "COPY LINK" + "DIALOG-TITLE": "Share", + "DESCRIPTION": "Clicking the link bellow, will copy it to the clipboard.", + "TITLE": "Link to share", + "EXPIRES": "Expires on", + "CLIPBOARD-MESSAGE": "Link copied to the clipboard", + "CONFIRMATION": { + "DIALOG-TITLE": "Remove this shared link", + "MESSAGE": "This link will be deleted and a new link will be created next time this file is shared", + "CANCEL": "Cancel", + "REMOVE": "Remove" } }, "PERMISSION_MANAGER": { diff --git a/lib/content-services/index.ts b/lib/content-services/index.ts index 3a27a5bb17..3b3143fddb 100644 --- a/lib/content-services/index.ts +++ b/lib/content-services/index.ts @@ -30,5 +30,6 @@ export * from './dialogs/index'; export * from './folder-directive/index'; export * from './content-metadata/index'; export * from './permission-manager/index'; +export * from './content-node-share/index'; export * from './content.module'; diff --git a/lib/core/app-config/app-config.service.ts b/lib/core/app-config/app-config.service.ts index ec1f52808b..6b5992691e 100644 --- a/lib/core/app-config/app-config.service.ts +++ b/lib/core/app-config/app-config.service.ts @@ -26,6 +26,7 @@ export enum AppConfigValues { PROVIDERS = 'providers', OAUTHCONFIG = 'oauth2', ECMHOST = 'ecmHost', + BASESHAREURL = 'baseShareUrl', BPMHOST = 'bpmHost', AUTHTYPE = 'authType', CONTEXTROOTECM = 'contextRootEcm', diff --git a/lib/core/clipboard/clipboard.directive.spec.ts b/lib/core/clipboard/clipboard.directive.spec.ts new file mode 100644 index 0000000000..fd1848b613 --- /dev/null +++ b/lib/core/clipboard/clipboard.directive.spec.ts @@ -0,0 +1,67 @@ +/*! + * @license + * Copyright 2016 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 { Component } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { setupTestBed } from '../testing/setupTestBed'; +import { CoreModule } from '../core.module'; +import { ClipboardService } from './clipboard.service'; + +@Component({ + selector: 'adf-test-component', + template: ` + + + + ` +}) +class TestComponent {} + +describe('ClipboardDirective', () => { + let fixture: ComponentFixture; + let clipboardService: ClipboardService; + + setupTestBed({ + imports: [ + CoreModule.forRoot() + ], + declarations: [ + TestComponent + ], + providers: [ + ClipboardService + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TestComponent); + clipboardService = TestBed.get(ClipboardService); + fixture.detectChanges(); + }); + + it('should notify copy target value on button click event', () => { + spyOn(clipboardService, 'copyToClipboard'); + fixture.nativeElement.querySelector('input').value = 'some value'; + fixture.nativeElement.querySelector('button').dispatchEvent(new MouseEvent('click')); + + expect(clipboardService.copyToClipboard).toHaveBeenCalled(); + }); +}); diff --git a/lib/core/clipboard/clipboard.directive.ts b/lib/core/clipboard/clipboard.directive.ts new file mode 100644 index 0000000000..ddf2cd6e70 --- /dev/null +++ b/lib/core/clipboard/clipboard.directive.ts @@ -0,0 +1,47 @@ +/*! + * @license + * Copyright 2016 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 { Directive, Input, HostListener } from '@angular/core'; +import { ClipboardService } from './clipboard.service'; + +@Directive({ + selector: '[adf-clipboard]', + exportAs: 'adfClipboard' +}) +export class ClipboardDirective { + // tslint:disable-next-line:no-input-rename + @Input('adf-clipboard') target: HTMLInputElement | HTMLTextAreaElement; + + // tslint:disable-next-line:no-input-rename + @Input('clipboard-notification') message: string; + + @HostListener('click', ['$event']) + handleClickEvent(event: MouseEvent) { + event.preventDefault(); + this.copyToClipboard(); + } + + constructor(private clipboardService: ClipboardService) {} + + private copyToClipboard() { + const isValidTarget = this.clipboardService.isTargetValid(this.target); + + if (isValidTarget) { + this.clipboardService.copyToClipboard(this.target, this.message); + } + } +} diff --git a/lib/core/clipboard/clipboard.module.ts b/lib/core/clipboard/clipboard.module.ts new file mode 100644 index 0000000000..9eb83fa08e --- /dev/null +++ b/lib/core/clipboard/clipboard.module.ts @@ -0,0 +1,38 @@ +/*! + * @license + * Copyright 2016 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 { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ClipboardDirective } from './clipboard.directive'; +import { ClipboardService } from './clipboard.service'; + +@NgModule({ + imports: [ + CommonModule + ], + providers: [ + ClipboardService + ], + declarations: [ + ClipboardDirective + ], + exports: [ + ClipboardDirective + ] +}) + +export class ClipboardModule {} diff --git a/lib/core/clipboard/clipboard.service.spec.ts b/lib/core/clipboard/clipboard.service.spec.ts new file mode 100644 index 0000000000..3f9fcee036 --- /dev/null +++ b/lib/core/clipboard/clipboard.service.spec.ts @@ -0,0 +1,91 @@ +/*! + * @license + * Copyright 2016 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 { LogService } from '../services/log.service'; +import { NotificationService } from '../services/notification.service'; +import { AppConfigService } from '../app-config/app-config.service'; +import { TestBed } from '@angular/core/testing'; +import { ClipboardModule } from './clipboard.module'; +import { ClipboardService } from './clipboard.service'; + +describe('ClipboardService', () => { + let clipboardService: ClipboardService; + let notificationService: NotificationService; + let inputElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + ClipboardModule + ], + providers: [ + LogService, + NotificationService, + { + provide: AppConfigService, + useValue: new AppConfigService(null) + + }, + { + provide: NotificationService, + useValue: new NotificationService(null, null) + } + ] + }); + }); + + beforeEach(() => { + clipboardService = TestBed.get(ClipboardService); + notificationService = TestBed.get(NotificationService); + inputElement = document.createElement('input'); + }); + + it('should validate target when element is input', () => { + const isValid = clipboardService.isTargetValid(inputElement); + expect(isValid).toBe(true); + }); + + it('should invalidate target when element is input and disabled', () => { + inputElement.disabled = true; + const isValid = clipboardService.isTargetValid(inputElement); + expect(isValid).toBe(false); + }); + + it('should copy text to clipboard', () => { + spyOn(document, 'execCommand'); + spyOn(inputElement, 'select'); + spyOn(inputElement, 'setSelectionRange'); + + inputElement.value = 'some text'; + + clipboardService.copyToClipboard(inputElement); + + expect(inputElement.select).toHaveBeenCalledWith(); + expect(inputElement.setSelectionRange) + .toHaveBeenCalledWith(0, inputElement.value.length); + expect(document.execCommand).toHaveBeenCalledWith('copy'); + }); + + it('should notify copy to clipboard with message', () => { + spyOn(notificationService, 'openSnackMessage'); + inputElement.value = 'some text'; + + clipboardService.copyToClipboard(inputElement, 'success'); + + expect(notificationService.openSnackMessage).toHaveBeenCalledWith('success'); + }); +}); diff --git a/lib/core/clipboard/clipboard.service.ts b/lib/core/clipboard/clipboard.service.ts new file mode 100644 index 0000000000..6ddec1d17c --- /dev/null +++ b/lib/core/clipboard/clipboard.service.ts @@ -0,0 +1,62 @@ +/*! + * @license + * Copyright 2016 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 { Injectable, Inject } from '@angular/core'; +import { DOCUMENT } from '@angular/platform-browser'; +import { LogService } from '../services/log.service'; +import { NotificationService } from '../services/notification.service'; + +@Injectable() +export class ClipboardService { + + constructor( + @Inject(DOCUMENT) private document: any, + private logService: LogService, + private notificationService: NotificationService) {} + + isTargetValid(target: HTMLInputElement | HTMLTextAreaElement) { + if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) { + if (target.hasAttribute('disabled')) { + return false; + } + return true; + } + + this.logService.error(`${target} should be input or textarea`); + return false; + } + + copyToClipboard(target: HTMLInputElement | HTMLTextAreaElement, message?: string) { + if (this.isTargetValid(target)) { + try { + target.select(); + target.setSelectionRange(0, target.value.length); + this.document.execCommand('copy'); + this.notify(message); + } catch (error) { + this.logService.error(error); + } + } + } + + private notify(message) { + if (message) { + this.notificationService.openSnackMessage(message); + } + } + +} diff --git a/lib/core/clipboard/index.ts b/lib/core/clipboard/index.ts new file mode 100644 index 0000000000..4c6ac1d58f --- /dev/null +++ b/lib/core/clipboard/index.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright 2016 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. + */ + +export * from './public-api'; diff --git a/lib/core/clipboard/public-api.ts b/lib/core/clipboard/public-api.ts new file mode 100644 index 0000000000..558c6f63be --- /dev/null +++ b/lib/core/clipboard/public-api.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright 2016 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. + */ + +// export * from './accordion-group.component'; +export * from './clipboard.directive'; +export * from './clipboard.service'; + +export * from './clipboard.module'; diff --git a/lib/core/core.module.ts b/lib/core/core.module.ts index e38c8051fc..ab28312765 100644 --- a/lib/core/core.module.ts +++ b/lib/core/core.module.ts @@ -43,6 +43,7 @@ import { SidenavLayoutModule } from './layout/layout.module'; import { CommentsModule } from './comments/comments.module'; import { ButtonsMenuModule } from './buttons-menu/buttons-menu.module'; import { TemplateModule } from './templates/template.module'; +import { ClipboardModule } from './clipboard/clipboard.module'; import { DirectiveModule } from './directives/directive.module'; import { PipeModule } from './pipes/pipe.module'; @@ -166,6 +167,7 @@ export function providers() { PipeModule, CommonModule, DirectiveModule, + ClipboardModule, FormsModule, ReactiveFormsModule, HttpClientModule, @@ -203,6 +205,7 @@ export function providers() { PipeModule, CommonModule, DirectiveModule, + ClipboardModule, FormsModule, ReactiveFormsModule, HttpClientModule, @@ -276,6 +279,7 @@ export class CoreModuleLazy { PipeModule, CommonModule, DirectiveModule, + ClipboardModule, FormsModule, ReactiveFormsModule, HttpClientModule, diff --git a/lib/core/index.ts b/lib/core/index.ts index 23669abccf..506b8ba508 100644 --- a/lib/core/index.ts +++ b/lib/core/index.ts @@ -39,6 +39,7 @@ export * from './templates/index'; export * from './pipes/index'; export * from './services/index'; export * from './directives/index'; +export * from './clipboard/index'; export * from './utils/index'; export * from './interface/index';