[ADF-4496] Share dialog enhancements (#4705)

* raise snackbars on unshare errors

* remove time from datepicker

* update tests

* Update en.json

* code updates as per review

* update docs

* bind datetimepicker type attribute

* set datetimepicker type by configuration or default

* tests

* e2e test

* update docs

* e2e set sharedLinkDateTimePickerType config
This commit is contained in:
Denys Vuika 2019-06-06 16:32:37 +01:00 committed by Eugenio Romano
parent c3b1300d86
commit 99f4b07878
24 changed files with 381 additions and 121 deletions

View File

@ -67,3 +67,17 @@ and passes it to a [Viewer component](../../core/components/viewer.component.md)
[allowGoBack]="false"> [allowGoBack]="false">
</adf-viewer> </adf-viewer>
``` ```
## Date and time widget
Date and time widget for setting the expiration date can be configured to show only the date picker or both date and time piker.
By default, the widget will show both date and time picker if `sharedLinkDateTimePickerType` is not present in the app.config.json.
Possible values are `'date'` or `'datetime'`
```json
{
...
"sharedLinkDateTimePickerType": 'date'
...
}
```

View File

@ -27,6 +27,12 @@ Finds shared links to Content Services items.
- _options:_ `any` - Options supported by JS-API - _options:_ `any` - Options supported by JS-API
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/NodePaging.md)`>` - List of shared links - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/NodePaging.md)`>` - List of shared links
### Events
| Name | Type | Description |
| --- | --- | --- |
| error | `Subject<{ statusCode: number, message: string }>` | Gets emitted upon errors. |
## Details ## Details
Content Services allows users to generate URLs that can be shared with Content Services allows users to generate URLs that can be shared with

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { LoginPage, BrowserActions } from '@alfresco/adf-testing'; import { LoginPage, BrowserActions, LocalStorageUtil } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../pages/adf/contentServicesPage'; import { ContentServicesPage } from '../../pages/adf/contentServicesPage';
import { NavigationBarPage } from '../../pages/adf/navigationBarPage'; import { NavigationBarPage } from '../../pages/adf/navigationBarPage';
import { ViewerPage } from '../../pages/adf/viewerPage'; import { ViewerPage } from '../../pages/adf/viewerPage';
@ -140,6 +140,22 @@ describe('Share file', () => {
shareDialog.clickDateTimePickerButton(); shareDialog.clickDateTimePickerButton();
shareDialog.calendarTodayDayIsDisabled(); shareDialog.calendarTodayDayIsDisabled();
}); });
it('[C310329] Should be possible to set expiry date only for link', async () => {
await LocalStorageUtil.setConfigField('sharedLinkDateTimePickerType', JSON.stringify('date'));
contentServicesPage.clickShareButton();
shareDialog.checkDialogIsDisplayed();
shareDialog.clickDateTimePickerButton();
shareDialog.setDefaultDay();
shareDialog.dateTimePickerDialogIsClosed();
const value = await shareDialog.getExpirationDate();
shareDialog.clickCloseButton();
shareDialog.dialogIsClosed();
contentServicesPage.clickShareButton();
shareDialog.checkDialogIsDisplayed();
shareDialog.expirationDateInputHasValue(value);
BrowserActions.closeMenuAndDialogs();
});
}); });
describe('Shared link preview', () => { describe('Shared link preview', () => {

View File

@ -1,6 +1,5 @@
<div class="adf-share-link__dialog-content"> <div class="adf-share-link__dialog-content">
<h1 data-automation-id="adf-share-dialog-title" <h1 data-automation-id="adf-share-dialog-title" class="adf-share-link__title">
class="adf-share-link__title">
{{ 'SHARE.DIALOG-TITLE' | translate }} {{ fileName }} {{ 'SHARE.DIALOG-TITLE' | translate }} {{ fileName }}
</h1> </h1>
@ -10,50 +9,47 @@
<div class="adf-share-link--row"> <div class="adf-share-link--row">
<h1 class="adf-share-link__label">{{ 'SHARE.TITLE' | translate }}</h1> <h1 class="adf-share-link__label">{{ 'SHARE.TITLE' | translate }}</h1>
<mat-slide-toggle <mat-slide-toggle color="primary" data-automation-id="adf-share-toggle" [checked]="isFileShared"
color="primary" [disabled]="!canUpdate || isDisabled" (change)="onSlideShareChange($event)">
data-automation-id="adf-share-toggle"
[checked]="isFileShared"
[disabled]="!canUpdate || isDisabled"
(change)="onSlideShareChange($event)">
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<form [formGroup]="form"> <form [formGroup]="form">
<mat-form-field class="adf-full-width"> <mat-form-field class="adf-full-width">
<input #sharedLinkInput <input #sharedLinkInput data-automation-id="adf-share-link" class="adf-share-link__input" matInput
data-automation-id="adf-share-link" cdkFocusInitial placeholder="{{ 'SHARE.PUBLIC-LINK' | translate }}" formControlName="sharedUrl"
class="adf-share-link__input"
matInput
cdkFocusInitial
placeholder="{{ 'SHARE.PUBLIC-LINK' | translate }}"
formControlName="sharedUrl"
readonly="readonly"> readonly="readonly">
<mat-icon class="adf-input-action" matSuffix <mat-icon class="adf-input-action" matSuffix
[clipboard-notification]="'SHARE.CLIPBOARD-MESSAGE' | translate" [clipboard-notification]="'SHARE.CLIPBOARD-MESSAGE' | translate" [adf-clipboard]
[adf-clipboard] [target]="sharedLinkInput"> [target]="sharedLinkInput">
link link
</mat-icon> </mat-icon>
</mat-form-field> </mat-form-field>
<div class="adf-share-link--row">
<h1 class="adf-share-link__label">{{ 'SHARE.EXPIRES' | translate }}</h1> <h1 class="adf-share-link__label">{{ 'SHARE.EXPIRES' | translate }}</h1>
<mat-slide-toggle [disabled]="!canUpdate" #slideToggleExpirationDate color="primary"
data-automation-id="adf-expire-toggle" [checked]="form.controls['time'].value"
(change)="onToggleExpirationDate($event)">
</mat-slide-toggle>
</div>
<mat-form-field class="adf-full-width"> <mat-form-field class="adf-full-width">
<mat-datetimepicker-toggle [for]="datetimePicker" matSuffix></mat-datetimepicker-toggle> <mat-datetimepicker-toggle #matDatetimepickerToggle="matDatetimepickerToggle" [for]="datetimePicker" matSuffix></mat-datetimepicker-toggle>
<mat-datetimepicker #datetimePicker type="datetime" openOnFocus="true" timeInterval="1"></mat-datetimepicker> <mat-datetimepicker #datetimePicker (closed)="onDatetimepickerClosed()" [type]="type" openOnFocus="true" timeInterval="1"></mat-datetimepicker>
<input class="adf-share-link__input" <input class="adf-share-link__input"
#dateTimePickerInput
matInput matInput
[min]="minDate" [min]="minDate"
formControlName="time" formControlName="time"
[matDatetimepicker]="datetimePicker"> [matDatetimepicker]="datetimePicker" />
</mat-form-field> </mat-form-field>
</form> </form>
</mat-dialog-content> </mat-dialog-content>
<div mat-dialog-actions> <div mat-dialog-actions>
<button <button data-automation-id="adf-share-dialog-close" mat-button color="primary" mat-dialog-close>
data-automation-id="adf-share-dialog-close"
mat-button color="primary" mat-dialog-close>
{{ 'SHARE.CLOSE' | translate }} {{ 'SHARE.CLOSE' | translate }}
</button> </button>
</div> </div>
</div> </div>

View File

@ -16,17 +16,19 @@
*/ */
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TestBed, fakeAsync, async } from '@angular/core/testing'; import { TestBed, fakeAsync, async, ComponentFixture } from '@angular/core/testing';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material'; import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material';
import { of, empty } from 'rxjs'; import { of, empty } from 'rxjs';
import { import {
setupTestBed, setupTestBed,
CoreModule,
SharedLinksApiService, SharedLinksApiService,
NodesApiService, NodesApiService,
NotificationService, NotificationService,
RenditionsService RenditionsService,
AppConfigService
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { CoreTestingModule } from '../../core/testing/core.testing.module';
import { AppConfigServiceMock } from '../../core/mock/app-config.service.mock';
import { ContentNodeShareModule } from './content-node-share.module'; import { ContentNodeShareModule } from './content-node-share.module';
import { ShareDialogComponent } from './content-node-share.dialog'; import { ShareDialogComponent } from './content-node-share.dialog';
import moment from 'moment-es6'; import moment from 'moment-es6';
@ -40,18 +42,20 @@ describe('ShareDialogComponent', () => {
let sharedLinksApiService: SharedLinksApiService; let sharedLinksApiService: SharedLinksApiService;
let renditionService: RenditionsService; let renditionService: RenditionsService;
let nodesApiService: NodesApiService; let nodesApiService: NodesApiService;
let fixture; let fixture: ComponentFixture<ShareDialogComponent>;
let component; let component: ShareDialogComponent;
let appConfigService: AppConfigService;
setupTestBed({ setupTestBed({
imports: [ imports: [
NoopAnimationsModule, NoopAnimationsModule,
CoreModule.forRoot(), CoreTestingModule,
ContentNodeShareModule ContentNodeShareModule
], ],
providers: [ providers: [
NodesApiService, NodesApiService,
SharedLinksApiService, SharedLinksApiService,
{ provide: AppConfigService, useClass: AppConfigServiceMock },
{ provide: NotificationService, useValue: notificationServiceMock }, { provide: NotificationService, useValue: notificationServiceMock },
{ provide: MatDialogRef, useValue: { close: () => {}} }, { provide: MatDialogRef, useValue: { close: () => {}} },
{ provide: MAT_DIALOG_DATA, useValue: {} } { provide: MAT_DIALOG_DATA, useValue: {} }
@ -64,10 +68,9 @@ describe('ShareDialogComponent', () => {
sharedLinksApiService = TestBed.get(SharedLinksApiService); sharedLinksApiService = TestBed.get(SharedLinksApiService);
renditionService = TestBed.get(RenditionsService); renditionService = TestBed.get(RenditionsService);
nodesApiService = TestBed.get(NodesApiService); nodesApiService = TestBed.get(NodesApiService);
appConfigService = TestBed.get(AppConfigService);
component = fixture.componentInstance; component = fixture.componentInstance;
});
beforeEach(() => {
node = { node = {
entry: { entry: {
id: 'nodeId', id: 'nodeId',
@ -78,6 +81,38 @@ describe('ShareDialogComponent', () => {
}; };
}); });
describe('Error Handling', () => {
it('should emit a generic error when unshare fails', (done) => {
spyOn(sharedLinksApiService, 'deleteSharedLink').and.returnValue(
of(new Error(`{ "error": { "statusCode": 999 } }`))
);
const sub = sharedLinksApiService.error.subscribe((err) => {
expect(err.statusCode).toBe(999);
expect(err.message).toBe('SHARE.UNSHARE_ERROR');
sub.unsubscribe();
done();
});
component.deleteSharedLink('guid');
});
it('should emit permission error when unshare fails', (done) => {
spyOn(sharedLinksApiService, 'deleteSharedLink').and.returnValue(
of(new Error(`{ "error": { "statusCode": 403 } }`))
);
const sub = sharedLinksApiService.error.subscribe((err) => {
expect(err.statusCode).toBe(403);
expect(err.message).toBe('SHARE.UNSHARE_PERMISSION_ERROR');
sub.unsubscribe();
done();
});
component.deleteSharedLink('guid');
});
});
it(`should toggle share action when property 'sharedId' does not exists`, () => { it(`should toggle share action when property 'sharedId' does not exists`, () => {
spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(of({ spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(of({
entry: { id: 'sharedId', sharedId: 'sharedId' } entry: { id: 'sharedId', sharedId: 'sharedId' }
@ -196,21 +231,126 @@ describe('ShareDialogComponent', () => {
expect(fixture.nativeElement.querySelector('mat-datetimepicker-toggle button').disabled).toBe(true); expect(fixture.nativeElement.querySelector('mat-datetimepicker-toggle button').disabled).toBe(true);
}); });
it('should not update shared node expiryDate property when value changes', () => { it('should reset expiration date when toggle is unchecked', () => {
const date = moment(); spyOn(nodesApiService, 'updateNode').and.returnValue(of({}));
node.entry.properties['qshare:sharedId'] = 'sharedId'; node.entry.properties['qshare:sharedId'] = 'sharedId';
spyOn(nodesApiService, 'updateNode'); node.entry.properties['qshare:sharedId'] = '2017-04-15T18:31:37+00:00';
fixture.componentInstance.form.controls['time'].setValue(null); node.entry.allowableOperations = ['update'];
component.data = { component.data = {
node, node,
baseShareUrl: 'some-url/' baseShareUrl: 'some-url/'
}; };
fixture.detectChanges(); fixture.detectChanges();
component.form.controls['time'].setValue(moment());
fixture.detectChanges();
fixture.nativeElement
.querySelector(
'.mat-slide-toggle[data-automation-id="adf-expire-toggle"] label'
)
.dispatchEvent(new MouseEvent('click'));
fixture.detectChanges();
expect(nodesApiService.updateNode).toHaveBeenCalledWith('nodeId', {
properties: { 'qshare:expiryDate': null }
});
expect(
fixture.nativeElement.querySelector('input[formcontrolname="time"]').value
).toBe('');
});
it('should not allow expiration date action when node has no update permission', () => {
node.entry.properties['qshare:sharedId'] = 'sharedId';
node.entry.allowableOperations = [];
component.data = {
node,
baseShareUrl: 'some-url/'
};
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('input[formcontrolname="time"]').disabled).toBe(true);
expect(fixture.nativeElement.querySelector('.mat-slide-toggle[data-automation-id="adf-expire-toggle"]')
.classList).toContain('mat-disabled');
});
it('should update node expiration date with selected date', () => {
const date = moment();
node.entry.allowableOperations = ['update'];
node.entry.properties['qshare:sharedId'] = 'sharedId';
spyOn(nodesApiService, 'updateNode').and.returnValue(of({}));
fixture.componentInstance.form.controls['time'].setValue(null);
component.data = {
node,
baseShareUrl: 'some-url/'
};
fixture.detectChanges();
fixture.nativeElement
.querySelector(
'mat-slide-toggle[data-automation-id="adf-expire-toggle"] label'
)
.dispatchEvent(new MouseEvent('click'));
fixture.componentInstance.form.controls['time'].setValue(date); fixture.componentInstance.form.controls['time'].setValue(date);
fixture.detectChanges(); fixture.detectChanges();
expect(nodesApiService.updateNode).toHaveBeenCalled(); expect(nodesApiService.updateNode).toHaveBeenCalledWith('nodeId', {
properties: { 'qshare:expiryDate': date.utc().format() }
});
});
describe('datetimepicker type', () => {
beforeEach(() => {
spyOn(nodesApiService, 'updateNode').and.returnValue(of({}));
spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(of({}));
node.entry.allowableOperations = ['update'];
component.data = {
node,
baseShareUrl: 'some-url/'
};
});
it('it should update node with input date and end of day time when type is `date`', () => {
const dateTimePickerType = 'date';
const date = moment('2525-01-01 13:00:00');
spyOn(appConfigService, 'get').and.callFake(() => dateTimePickerType);
fixture.detectChanges();
fixture.nativeElement.querySelector('mat-slide-toggle[data-automation-id="adf-expire-toggle"] label')
.dispatchEvent(new MouseEvent('click'));
fixture.componentInstance.form.controls['time'].setValue(date);
fixture.detectChanges();
expect(nodesApiService.updateNode).toHaveBeenCalledWith('nodeId', {
properties: { 'qshare:expiryDate': date.endOf('day').utc().format() }
});
});
it('it should update node with input date and time when type is `datetime`', () => {
const dateTimePickerType = 'datetime';
const date = moment('2525-01-01 13:00:00');
spyOn(appConfigService, 'get').and.callFake(() => dateTimePickerType);
fixture.detectChanges();
fixture.nativeElement.querySelector('mat-slide-toggle[data-automation-id="adf-expire-toggle"] label')
.dispatchEvent(new MouseEvent('click'));
fixture.componentInstance.form.controls['time'].setValue(date);
fixture.detectChanges();
expect(nodesApiService.updateNode).toHaveBeenCalledWith('nodeId', {
properties: { 'qshare:expiryDate': date.utc().format() }
});
});
}); });
}); });

View File

@ -21,18 +21,23 @@ import {
OnInit, OnInit,
ViewEncapsulation, ViewEncapsulation,
ViewChild, ViewChild,
ElementRef,
OnDestroy OnDestroy
} from '@angular/core'; } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef, MatDialog } from '@angular/material'; import { MAT_DIALOG_DATA, MatDialogRef, MatDialog, MatSlideToggleChange } from '@angular/material';
import { FormGroup, FormControl } from '@angular/forms'; import { FormGroup, FormControl } from '@angular/forms';
import { Subscription, Observable } from 'rxjs'; import { Subscription, Observable, throwError } from 'rxjs';
import { tap, skip } from 'rxjs/operators'; import {
skip,
distinctUntilChanged,
mergeMap,
catchError
} from 'rxjs/operators';
import { import {
SharedLinksApiService, SharedLinksApiService,
NodesApiService, NodesApiService,
ContentService, ContentService,
RenditionsService RenditionsService,
AppConfigService
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { SharedLinkEntry, Node } from '@alfresco/js-api'; import { SharedLinkEntry, Node } from '@alfresco/js-api';
import { ConfirmDialogComponent } from '../dialogs/confirm.dialog'; import { ConfirmDialogComponent } from '../dialogs/confirm.dialog';
@ -42,7 +47,7 @@ import moment from 'moment-es6';
selector: 'adf-share-dialog', selector: 'adf-share-dialog',
templateUrl: './content-node-share.dialog.html', templateUrl: './content-node-share.dialog.html',
styleUrls: ['./content-node-share.dialog.scss'], styleUrls: ['./content-node-share.dialog.scss'],
host: { 'class': 'adf-share-dialog' }, host: { class: 'adf-share-dialog' },
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class ShareDialogComponent implements OnInit, OnDestroy { export class ShareDialogComponent implements OnInit, OnDestroy {
@ -55,34 +60,49 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
isFileShared: boolean = false; isFileShared: boolean = false;
isDisabled: boolean = false; isDisabled: boolean = false;
form: FormGroup = new FormGroup({ form: FormGroup = new FormGroup({
'sharedUrl': new FormControl(''), sharedUrl: new FormControl(''),
'time': new FormControl({value: '', disabled: false}) time: new FormControl({ value: '', disabled: false })
}); });
type = 'datetime';
@ViewChild('sharedLinkInput') sharedLinkInput: ElementRef; @ViewChild('matDatetimepickerToggle')
matDatetimepickerToggle;
@ViewChild('slideToggleExpirationDate')
slideToggleExpirationDate;
@ViewChild('dateTimePickerInput')
dateTimePickerInput;
constructor( constructor(
private appConfigService: AppConfigService,
private sharedLinksApiService: SharedLinksApiService, private sharedLinksApiService: SharedLinksApiService,
private dialogRef: MatDialogRef<ShareDialogComponent>, private dialogRef: MatDialogRef<ShareDialogComponent>,
private dialog: MatDialog, private dialog: MatDialog,
private nodesApiService: NodesApiService, private nodesApiService: NodesApiService,
private contentService: ContentService, private contentService: ContentService,
private renditionService: RenditionsService, private renditionService: RenditionsService,
@Inject(MAT_DIALOG_DATA) public data: any) { @Inject(MAT_DIALOG_DATA) public data: any
} ) {}
ngOnInit() { ngOnInit() {
this.type = this.appConfigService.get<string>('sharedLinkDateTimePickerType', 'datetime');
if (!this.canUpdate) { if (!this.canUpdate) {
this.form.controls['time'].disable(); this.form.controls['time'].disable();
} }
this.subscriptions.push( this.subscriptions.push(
this.form.valueChanges this.form.controls.time.valueChanges
.pipe( .pipe(
skip(1), skip(1),
tap((updates) => { distinctUntilChanged(),
this.updateNode(updates); mergeMap(
(updates) => this.updateNode(updates),
(formUpdates) => formUpdates
),
catchError((error) => {
return throwError(error);
}) })
) )
.subscribe((updates) => this.updateEntryExpiryDate(updates)) .subscribe((updates) => this.updateEntryExpiryDate(updates))
@ -94,9 +114,7 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
const properties = this.data.node.entry.properties; const properties = this.data.node.entry.properties;
if (properties && !properties['qshare:sharedId']) { if (properties && !properties['qshare:sharedId']) {
this.createSharedLinks(this.data.node.entry.id); this.createSharedLinks(this.data.node.entry.id);
} else { } else {
this.sharedId = properties['qshare:sharedId']; this.sharedId = properties['qshare:sharedId'];
this.isFileShared = true; this.isFileShared = true;
@ -123,7 +141,27 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
} }
get canUpdate() { get canUpdate() {
return this.contentService.hasAllowableOperations(this.data.node.entry, 'update'); return this.contentService.hasAllowableOperations(
this.data.node.entry,
'update'
);
}
onToggleExpirationDate(slideToggle: MatSlideToggleChange) {
if (slideToggle.checked) {
this.matDatetimepickerToggle.datetimepicker.open();
} else {
this.matDatetimepickerToggle.datetimepicker.close();
this.form.controls.time.setValue(null);
}
}
onDatetimepickerClosed() {
this.dateTimePickerInput.nativeElement.blur();
if (!this.form.controls.time.value) {
this.slideToggleExpirationDate.checked = false;
}
} }
private openConfirmationDialog() { private openConfirmationDialog() {
@ -140,7 +178,8 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
minWidth: '250px', minWidth: '250px',
closeOnNavigation: true closeOnNavigation: true
}) })
.beforeClose().subscribe((deleteSharedLink) => { .beforeClose()
.subscribe((deleteSharedLink) => {
if (deleteSharedLink) { if (deleteSharedLink) {
this.deleteSharedLink(this.sharedId); this.deleteSharedLink(this.sharedId);
} else { } else {
@ -152,15 +191,18 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
private createSharedLinks(nodeId: string) { private createSharedLinks(nodeId: string) {
this.isDisabled = true; this.isDisabled = true;
this.sharedLinksApiService.createSharedLinks(nodeId) this.sharedLinksApiService.createSharedLinks(nodeId).subscribe(
.subscribe((sharedLink: SharedLinkEntry) => { (sharedLink: SharedLinkEntry) => {
if (sharedLink.entry) { if (sharedLink.entry) {
this.sharedId = sharedLink.entry.id; this.sharedId = sharedLink.entry.id;
this.data.node.entry.properties['qshare:sharedId'] = this.sharedId; this.data.node.entry.properties[
'qshare:sharedId'
] = this.sharedId;
this.isDisabled = false; this.isDisabled = false;
this.isFileShared = true; this.isFileShared = true;
this.renditionService.generateRenditionForNode(this.data.node.entry.id).subscribe(() => {}); this.renditionService
.generateRenditionForNode(this.data.node.entry.id)
.subscribe(() => {});
this.updateForm(); this.updateForm();
} }
@ -168,21 +210,48 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
() => { () => {
this.isDisabled = false; this.isDisabled = false;
this.isFileShared = false; this.isFileShared = false;
}); }
);
} }
private deleteSharedLink(sharedId: string) { deleteSharedLink(sharedId: string) {
this.isDisabled = true; this.isDisabled = true;
this.sharedLinksApiService.deleteSharedLink(sharedId).subscribe(() => { this.sharedLinksApiService.deleteSharedLink(sharedId).subscribe(
this.data.node.entry.properties['qshare:sharedId'] = null; (response: any) => {
this.data.node.entry.properties['qshare:expiryDate'] = null; if (response instanceof Error) {
this.dialogRef.close(false); this.isDisabled = false;
this.isFileShared = true;
this.handleError(response);
} else {
this.data.node.entry.properties['qshare:sharedId'] = null;
this.data.node.entry.properties['qshare:expiryDate'] = null;
this.dialogRef.close(false);
}
}, },
() => { () => {
this.isDisabled = false; this.isDisabled = false;
this.isFileShared = false; this.isFileShared = false;
}); }
);
}
private handleError(error: Error) {
let message = 'SHARE.UNSHARE_ERROR';
let statusCode = 0;
try {
statusCode = JSON.parse(error.message).error.statusCode;
} catch {}
if (statusCode === 403) {
message = 'SHARE.UNSHARE_PERMISSION_ERROR';
}
this.sharedLinksApiService.error.next({
statusCode,
message
});
} }
private updateForm() { private updateForm() {
@ -190,27 +259,26 @@ export class ShareDialogComponent implements OnInit, OnDestroy {
const expiryDate = entry.properties['qshare:expiryDate']; const expiryDate = entry.properties['qshare:expiryDate'];
this.form.setValue({ this.form.setValue({
'sharedUrl': `${this.baseShareUrl}${this.sharedId}`, sharedUrl: `${this.baseShareUrl}${this.sharedId}`,
'time': expiryDate ? expiryDate : null time: expiryDate ? moment(expiryDate).local() : null
}); });
} }
private updateNode(updates): Observable<Node> { private updateNode(date: moment.Moment): Observable<Node> {
return this.nodesApiService.updateNode( return this.nodesApiService.updateNode(this.data.node.entry.id, {
this.data.node.entry.id, properties: {
{ 'qshare:expiryDate': date ?
properties: { (this.type === 'date' ? date.endOf('day').utc().format() : date.utc().format()) :
'qshare:expiryDate': updates.time ? updates.time.utc().format() : null null
}
} }
); });
} }
private updateEntryExpiryDate(updates) { private updateEntryExpiryDate(date: moment.Moment) {
const { properties } = this.data.node.entry; const { properties } = this.data.node.entry;
properties['qshare:expiryDate'] = updates.time properties['qshare:expiryDate'] = date
? updates.time.local() ? date.local()
: null; : null;
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "سيتم حذف هذا الرابط وسيتم إنشاء رابط جديد في المرة التالية التي يتم فيها مشاركة هذا الملف", "MESSAGE": "سيتم حذف هذا الرابط وسيتم إنشاء رابط جديد في المرة التالية التي يتم فيها مشاركة هذا الملف",
"CANCEL": "إلغاء", "CANCEL": "إلغاء",
"REMOVE": "إزالة" "REMOVE": "إزالة"
} },
"UNSHARE_PERMISSION_ERROR": "ليس لديك إذن لإلغاء مشاركة هذا الملف"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "تم تحديث خصائص المكتبة" "LIBRARY_UPDATED": "تم تحديث خصائص المكتبة"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "Tento odkaz se odstraní a při dalším sdílení tohoto souboru se vytvoří odkaz nový", "MESSAGE": "Tento odkaz se odstraní a při dalším sdílení tohoto souboru se vytvoří odkaz nový",
"CANCEL": "Zrušit", "CANCEL": "Zrušit",
"REMOVE": "Odstranit" "REMOVE": "Odstranit"
} },
"UNSHARE_PERMISSION_ERROR": "Nemáte potřebná oprávnění pro zrušení sdílení tohoto souboru"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Vlastnosti knihovny byly upraveny" "LIBRARY_UPDATED": "Vlastnosti knihovny byly upraveny"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "Dette link bliver slettet, og der oprettes et nyt link, når filen deles næste gang", "MESSAGE": "Dette link bliver slettet, og der oprettes et nyt link, når filen deles næste gang",
"CANCEL": "Annuller", "CANCEL": "Annuller",
"REMOVE": "Fjern" "REMOVE": "Fjern"
} },
"UNSHARE_PERMISSION_ERROR": "Du har ikke tilladelse til at ophæve delingen af denne fil"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Egenskaberne for biblioteket er blevet opdateret" "LIBRARY_UPDATED": "Egenskaberne for biblioteket er blevet opdateret"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "Dieser Link wird gelöscht und ein neuer Link wird erstellt wenn diese Datei das nächste Mal geteilt wird.", "MESSAGE": "Dieser Link wird gelöscht und ein neuer Link wird erstellt wenn diese Datei das nächste Mal geteilt wird.",
"CANCEL": "Abbrechen", "CANCEL": "Abbrechen",
"REMOVE": "Entfernen" "REMOVE": "Entfernen"
} },
"UNSHARE_PERMISSION_ERROR": "Sie verfügen nicht über die Berechtigung die Freigabe für diese Datei aufzuheben"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Bibliothekseigenschaften aktualisiert" "LIBRARY_UPDATED": "Bibliothekseigenschaften aktualisiert"
} }
} }
} }

View File

@ -297,7 +297,9 @@
"MESSAGE": "This link will be deleted and a new link will be created next time this file is shared", "MESSAGE": "This link will be deleted and a new link will be created next time this file is shared",
"CANCEL": "Cancel", "CANCEL": "Cancel",
"REMOVE": "Remove" "REMOVE": "Remove"
} },
"UNSHARE_ERROR": "Error unsharing this file",
"UNSHARE_PERMISSION_ERROR": "You don't have permission to unshare this file"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {

View File

@ -292,7 +292,8 @@
"MESSAGE": "Este enlace será eliminado. Se creará un enlace nuevo la próxima vez que comparta este fichero.", "MESSAGE": "Este enlace será eliminado. Se creará un enlace nuevo la próxima vez que comparta este fichero.",
"CANCEL": "Cancelar", "CANCEL": "Cancelar",
"REMOVE": "Eliminar" "REMOVE": "Eliminar"
} },
"UNSHARE_PERMISSION_ERROR": "No tiene acceso para dejar de compartir este fichero"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Propiedades de la biblioteca actualizadas" "LIBRARY_UPDATED": "Propiedades de la biblioteca actualizadas"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "Tämä linkki poistetaan ja uusi linkki luodaan, kun tiedosto jaetaan seuraavan kerran", "MESSAGE": "Tämä linkki poistetaan ja uusi linkki luodaan, kun tiedosto jaetaan seuraavan kerran",
"CANCEL": "Peruuta", "CANCEL": "Peruuta",
"REMOVE": "Poista" "REMOVE": "Poista"
} },
"UNSHARE_PERMISSION_ERROR": "Sinulla ei ole oikeuksia peruuttaa tämän tiedoston jakamista"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Kirjaston ominaisuudet päivitettiin" "LIBRARY_UPDATED": "Kirjaston ominaisuudet päivitettiin"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "Ce lien sera supprimé et un nouveau lien sera créé lors du prochain partage de ce fichier", "MESSAGE": "Ce lien sera supprimé et un nouveau lien sera créé lors du prochain partage de ce fichier",
"CANCEL": "Annuler", "CANCEL": "Annuler",
"REMOVE": "Supprimer" "REMOVE": "Supprimer"
} },
"UNSHARE_PERMISSION_ERROR": "Vous n'avez pas les droits d'accès pour ne pas partager ce fichier"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Les propriétés de la bibliothèque ont été mises à jour" "LIBRARY_UPDATED": "Les propriétés de la bibliothèque ont été mises à jour"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "Il link verrà eliminato e verrà creato un nuovo link per il prossimo file condiviso", "MESSAGE": "Il link verrà eliminato e verrà creato un nuovo link per il prossimo file condiviso",
"CANCEL": "Annulla", "CANCEL": "Annulla",
"REMOVE": "Rimuovi" "REMOVE": "Rimuovi"
} },
"UNSHARE_PERMISSION_ERROR": "Non hai il permesso per rimuovere la condivisione di questo file"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Proprietà della raccolta aggiornate" "LIBRARY_UPDATED": "Proprietà della raccolta aggiornate"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "このリンクは削除され、次回このファイルを共有するときに新しいリンクが作成されます。", "MESSAGE": "このリンクは削除され、次回このファイルを共有するときに新しいリンクが作成されます。",
"CANCEL": "キャンセル", "CANCEL": "キャンセル",
"REMOVE": "削除" "REMOVE": "削除"
} },
"UNSHARE_PERMISSION_ERROR": "このファイルの共有を解除する権限がありません。"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "ライブラリのプロパティが更新されました" "LIBRARY_UPDATED": "ライブラリのプロパティが更新されました"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "Denne koblingen vil bli slettet, og en ny kobling vil bli opprettet neste gang denne filen deles", "MESSAGE": "Denne koblingen vil bli slettet, og en ny kobling vil bli opprettet neste gang denne filen deles",
"CANCEL": "Avbryt", "CANCEL": "Avbryt",
"REMOVE": "Fjern" "REMOVE": "Fjern"
} },
"UNSHARE_PERMISSION_ERROR": "Du har ikke tillatelse til å oppheve deling av denne filen"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Bibliotekegenskaper oppdatert" "LIBRARY_UPDATED": "Bibliotekegenskaper oppdatert"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "Deze koppeling wordt verwijderd en er wordt een nieuwe koppeling gemaakt de volgende keer dat dit bestand wordt gedeeld", "MESSAGE": "Deze koppeling wordt verwijderd en er wordt een nieuwe koppeling gemaakt de volgende keer dat dit bestand wordt gedeeld",
"CANCEL": "Annuleren", "CANCEL": "Annuleren",
"REMOVE": "Verwijderen" "REMOVE": "Verwijderen"
} },
"UNSHARE_PERMISSION_ERROR": "U hebt geen rechten om het delen van dit bestand ongedaan te maken"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Bibliotheekeigenschappen bijgewerkt" "LIBRARY_UPDATED": "Bibliotheekeigenschappen bijgewerkt"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "Ten link zostanie usunięty, a przy następnym udostępnieniu tego pliku zostanie utworzony nowy link.", "MESSAGE": "Ten link zostanie usunięty, a przy następnym udostępnieniu tego pliku zostanie utworzony nowy link.",
"CANCEL": "Anuluj", "CANCEL": "Anuluj",
"REMOVE": "Usuń" "REMOVE": "Usuń"
} },
"UNSHARE_PERMISSION_ERROR": "Nie masz uprawnień, aby cofnąć udostępnianie tego pliku"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Zaktualizowano właściwości biblioteki" "LIBRARY_UPDATED": "Zaktualizowano właściwości biblioteki"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "Este link será excluído. Um novo link será criado na próxima vez que esse arquivo for compartilhado.", "MESSAGE": "Este link será excluído. Um novo link será criado na próxima vez que esse arquivo for compartilhado.",
"CANCEL": "Cancelar", "CANCEL": "Cancelar",
"REMOVE": "Remover" "REMOVE": "Remover"
} },
"UNSHARE_PERMISSION_ERROR": "Você não tem permissão para descompartilhar deste arquivo"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Propriedades da biblioteca atualizadas" "LIBRARY_UPDATED": "Propriedades da biblioteca atualizadas"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "Эта ссылка будет удалена, и новая ссылка будет создана при следующем совместном использовании этого файла", "MESSAGE": "Эта ссылка будет удалена, и новая ссылка будет создана при следующем совместном использовании этого файла",
"CANCEL": "Отмена", "CANCEL": "Отмена",
"REMOVE": "Удалить" "REMOVE": "Удалить"
} },
"UNSHARE_PERMISSION_ERROR": "У вас нет разрешения на снятие этого файла с публикации"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Свойства библиотеки обновлены" "LIBRARY_UPDATED": "Свойства библиотеки обновлены"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "Den här länken kommer att raderas och en ny länk kommer att skapas nästa gång den här filen delas", "MESSAGE": "Den här länken kommer att raderas och en ny länk kommer att skapas nästa gång den här filen delas",
"CANCEL": "Avbryt", "CANCEL": "Avbryt",
"REMOVE": "Ta bort" "REMOVE": "Ta bort"
} },
"UNSHARE_PERMISSION_ERROR": "Du har inte behörighet att sluta dela den här filen"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "Biblioteksegenskaper uppdaterade" "LIBRARY_UPDATED": "Biblioteksegenskaper uppdaterade"
} }
} }
} }

View File

@ -292,7 +292,8 @@
"MESSAGE": "此链接将被删除,下次共享此文件时将会创建新链接", "MESSAGE": "此链接将被删除,下次共享此文件时将会创建新链接",
"CANCEL": "取消", "CANCEL": "取消",
"REMOVE": "删除" "REMOVE": "删除"
} },
"UNSHARE_PERMISSION_ERROR": "你没有权限取消这个文件的共享"
}, },
"PERMISSION_MANAGER": { "PERMISSION_MANAGER": {
"PERMISSION_DISPLAY": { "PERMISSION_DISPLAY": {
@ -363,4 +364,4 @@
"LIBRARY_UPDATED": "已更新库属性" "LIBRARY_UPDATED": "已更新库属性"
} }
} }
} }

View File

@ -17,7 +17,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { NodePaging, SharedLinkEntry } from '@alfresco/js-api'; import { NodePaging, SharedLinkEntry } from '@alfresco/js-api';
import { Observable, from, of } from 'rxjs'; import { Observable, from, of, Subject } from 'rxjs';
import { AlfrescoApiService } from './alfresco-api.service'; import { AlfrescoApiService } from './alfresco-api.service';
import { UserPreferencesService } from './user-preferences.service'; import { UserPreferencesService } from './user-preferences.service';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
@ -27,6 +27,8 @@ import { catchError } from 'rxjs/operators';
}) })
export class SharedLinksApiService { export class SharedLinksApiService {
error = new Subject<{ statusCode: number, message: string }>();
constructor(private apiService: AlfrescoApiService, constructor(private apiService: AlfrescoApiService,
private preferences: UserPreferencesService) { private preferences: UserPreferencesService) {
} }
@ -73,11 +75,11 @@ export class SharedLinksApiService {
* @param sharedId ID of the link to delete * @param sharedId ID of the link to delete
* @returns Null response notifying when the operation is complete * @returns Null response notifying when the operation is complete
*/ */
deleteSharedLink(sharedId: string): Observable<SharedLinkEntry> { deleteSharedLink(sharedId: string): Observable<any | Error> {
const promise = this.sharedLinksApi.deleteSharedLink(sharedId); const promise = this.sharedLinksApi.deleteSharedLink(sharedId);
return from(promise).pipe( return from(promise).pipe(
catchError((err) => of(err)) catchError((err: Error) => of(err))
); );
} }
} }