/*! * @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 { Observable, Subject, from } from 'rxjs'; import { Component, OnInit, Output, EventEmitter, OnDestroy, ViewEncapsulation } from '@angular/core'; import { FormBuilder, FormGroup, Validators, FormControl, AbstractControl } from '@angular/forms'; import { MatDialogRef } from '@angular/material'; import { SiteBodyCreate, SiteEntry, SitePaging } from '@alfresco/js-api'; import { AlfrescoApiService } from '@alfresco/adf-core'; import { debounceTime, mergeMap, takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-library-dialog', styleUrls: ['./library.dialog.scss'], templateUrl: './library.dialog.html', encapsulation: ViewEncapsulation.None, host: { class: 'adf-library-dialog' } }) export class LibraryDialogComponent implements OnInit, OnDestroy { /** Emitted when an error occurs. */ @Output() error: EventEmitter = new EventEmitter(); /** Emitted when the new library is created successfully. The * event parameter is a SiteEntry object with the details of the * newly-created library. */ @Output() success: EventEmitter = new EventEmitter(); onDestroy$: Subject = new Subject(); createTitle = 'LIBRARY.DIALOG.CREATE_TITLE'; libraryTitleExists = false; form: FormGroup; visibilityOption: any; visibilityOptions = [ { value: 'PUBLIC', label: 'LIBRARY.VISIBILITY.PUBLIC', disabled: false }, { value: 'PRIVATE', label: 'LIBRARY.VISIBILITY.PRIVATE', disabled: false }, { value: 'MODERATED', label: 'LIBRARY.VISIBILITY.MODERATED', disabled: false } ]; constructor( private alfrescoApiService: AlfrescoApiService, private formBuilder: FormBuilder, private dialog: MatDialogRef ) {} ngOnInit() { const validators = { id: [ Validators.required, Validators.maxLength(72), this.forbidSpecialCharacters ], title: [ Validators.required, this.forbidOnlySpaces, Validators.minLength(2), Validators.maxLength(256) ], description: [Validators.maxLength(512)] }; this.form = this.formBuilder.group({ title: [null, validators.title], id: [null, validators.id, this.createSiteIdValidator()], description: ['', validators.description] }); this.visibilityOption = this.visibilityOptions[0].value; this.form.controls['title'].valueChanges .pipe( debounceTime(300), mergeMap( (title) => this.checkLibraryNameExists(title), (title) => title ), takeUntil(this.onDestroy$) ) .subscribe((title: string) => { if (!this.form.controls['id'].dirty && this.canGenerateId(title)) { this.form.patchValue({ id: this.sanitize(title.trim()) }); this.form.controls['id'].markAsTouched(); } }); } ngOnDestroy() { this.onDestroy$.next(true); this.onDestroy$.complete(); } get title(): string { const { title } = this.form.value; return (title || '').trim(); } get id(): string { const { id } = this.form.value; return (id || '').trim(); } get description(): string { const { description } = this.form.value; return (description || '').trim(); } get visibility(): string { return this.visibilityOption || ''; } submit() { const { form, dialog } = this; if (!form.valid) { return; } this.create().subscribe( (node: SiteEntry) => { this.success.emit(node); dialog.close(node); }, (error) => this.handleError(error) ); } visibilityChangeHandler(event) { this.visibilityOption = event.value; } private create(): Observable { const { title, id, description, visibility } = this; const siteBody = { id, title, description, visibility }; return from(this.alfrescoApiService.sitesApi.createSite(siteBody)); } private sanitize(input: string) { return input.replace(/[\s\s]+/g, '-').replace(/[^A-Za-z0-9-]/g, ''); } private canGenerateId(title) { return Boolean(title.replace(/[^A-Za-z0-9-]/g, '').length); } private handleError(error: any): any { const { error: { statusCode } } = JSON.parse(error.message); if (statusCode === 409) { this.form.controls['id'].setErrors({ message: 'LIBRARY.ERRORS.CONFLICT' }); } return error; } private async checkLibraryNameExists(libraryTitle: string) { let entries = []; try { entries = (await this.findLibraryByTitle(libraryTitle)).list.entries; } catch { entries = []; } if (entries.length) { this.libraryTitleExists = entries[0].entry.title.toLowerCase() === libraryTitle.toLowerCase(); } else { this.libraryTitleExists = false; } } private async findLibraryByTitle(libraryTitle: string): Promise { return this.alfrescoApiService .getInstance() .core.queriesApi.findSites(libraryTitle, { maxItems: 1, fields: ['title'] }); } private forbidSpecialCharacters({ value }: FormControl) { if (value === null || value.length === 0) { return null; } const validCharacters: RegExp = /[^A-Za-z0-9-]/; const isValid: boolean = !validCharacters.test(value); return isValid ? null : { message: 'LIBRARY.ERRORS.ILLEGAL_CHARACTERS' }; } private forbidOnlySpaces({ value }: FormControl) { if (value === null || value.length === 0) { return null; } const isValid: boolean = !!(value || '').trim(); return isValid ? null : { message: 'LIBRARY.ERRORS.ONLY_SPACES' }; } private createSiteIdValidator() { let timer; return (control: AbstractControl) => { if (timer) { clearTimeout(timer); } return new Promise((resolve) => { timer = setTimeout(() => { return from( this.alfrescoApiService.sitesApi.getSite(control.value) ).subscribe( () => resolve({ message: 'LIBRARY.ERRORS.EXISTENT_SITE' }), () => resolve(null) ); }, 300); }); }; } }