diff --git a/lib/core/directives/directive.module.ts b/lib/core/directives/directive.module.ts
index c21c80554c..1f9ce7aee4 100644
--- a/lib/core/directives/directive.module.ts
+++ b/lib/core/directives/directive.module.ts
@@ -32,6 +32,8 @@ import { TooltipCardDirective } from './tooltip-card/tooltip-card.directive';
import { OverlayModule } from '@angular/cdk/overlay';
import { TooltipCardComponent } from './tooltip-card/tooltip-card.component';
import { InfiniteSelectScrollDirective } from './infinite-select-scroll.directive';
+import { LibraryFavoriteDirective } from './library-favorite.directive';
+import { LibraryMembershipDirective } from './library-membership.directive';
@NgModule({
imports: [
@@ -51,7 +53,9 @@ import { InfiniteSelectScrollDirective } from './infinite-select-scroll.directiv
VersionCompatibilityDirective,
TooltipCardDirective,
TooltipCardComponent,
- InfiniteSelectScrollDirective
+ InfiniteSelectScrollDirective,
+ LibraryFavoriteDirective,
+ LibraryMembershipDirective
],
exports: [
HighlightDirective,
@@ -64,7 +68,9 @@ import { InfiniteSelectScrollDirective } from './infinite-select-scroll.directiv
UploadDirective,
VersionCompatibilityDirective,
TooltipCardDirective,
- InfiniteSelectScrollDirective
+ InfiniteSelectScrollDirective,
+ LibraryFavoriteDirective,
+ LibraryMembershipDirective
]
})
export class DirectiveModule {}
diff --git a/lib/core/directives/library-favorite.directive.spec.ts b/lib/core/directives/library-favorite.directive.spec.ts
new file mode 100644
index 0000000000..e0d34253ca
--- /dev/null
+++ b/lib/core/directives/library-favorite.directive.spec.ts
@@ -0,0 +1,121 @@
+/*!
+ * @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 { Component, ViewChild } from '@angular/core';
+import { LibraryEntity, LibraryFavoriteDirective } from './library-favorite.directive';
+import { TestBed, ComponentFixture } from '@angular/core/testing';
+import { TranslateModule } from '@ngx-translate/core';
+import { AlfrescoApiService } from '../services/alfresco-api.service';
+import { CoreModule } from '../core.module';
+import { AlfrescoApiServiceMock } from '../mock';
+
+@Component({
+ selector: 'app-test-component',
+ template: ` `
+})
+class TestComponent {
+ @ViewChild('favoriteLibrary', { static: true })
+ directive: LibraryFavoriteDirective;
+
+ selection: LibraryEntity = null;
+}
+
+describe('LibraryFavoriteDirective', () => {
+ let fixture: ComponentFixture;
+ let api: AlfrescoApiService;
+ let component: TestComponent;
+ let selection: LibraryEntity;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [TranslateModule.forRoot(), CoreModule.forRoot()],
+ providers: [
+ { provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }
+ ],
+ declarations: [TestComponent, LibraryFavoriteDirective]
+ });
+ fixture = TestBed.createComponent(TestComponent);
+ component = fixture.componentInstance;
+ api = TestBed.inject(AlfrescoApiService);
+ selection = { entry: { guid: 'guid', id: 'id', title: 'Site', visibility: 'PUBLIC' }, isLibrary: true, isFavorite: false };
+ component.selection = selection;
+ });
+
+ it('should not check for favorite if no selection exists', () => {
+ spyOn(api.peopleApi, 'getFavoriteSite');
+ fixture.detectChanges();
+
+ expect(api.peopleApi.getFavoriteSite).not.toHaveBeenCalled();
+ });
+
+ it('should mark selection as favorite', async () => {
+ spyOn(api.peopleApi, 'getFavoriteSite').and.returnValue(Promise.resolve(null));
+
+ delete selection.isFavorite;
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(api.peopleApi.getFavoriteSite).toHaveBeenCalled();
+ expect(component.directive.isFavorite()).toBe(true);
+ });
+
+ it('should mark selection not favorite', async () => {
+ spyOn(api.peopleApi, 'getFavoriteSite').and.returnValue(Promise.reject());
+
+ delete selection.isFavorite;
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(api.peopleApi.getFavoriteSite).toHaveBeenCalled();
+ expect(component.directive.isFavorite()).toBe(false);
+ });
+
+ it('should call addFavorite() on click event when selection is not a favorite', async () => {
+ spyOn(api.peopleApi, 'getFavoriteSite').and.returnValue(Promise.reject());
+ spyOn(api.peopleApi, 'addFavorite').and.returnValue(Promise.resolve(null));
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(component.directive.isFavorite()).toBeFalsy();
+
+ fixture.nativeElement.querySelector('button').dispatchEvent(new MouseEvent('click'));
+ fixture.detectChanges();
+ expect(api.peopleApi.addFavorite).toHaveBeenCalled();
+ });
+
+ it('should call removeFavoriteSite() on click event when selection is favorite', async () => {
+ spyOn(api.peopleApi, 'getFavoriteSite').and.returnValue(Promise.resolve(null));
+ spyOn(api.favoritesApi, 'removeFavoriteSite').and.returnValue(Promise.resolve());
+
+ selection.isFavorite = true;
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(component.directive.isFavorite()).toBeTruthy();
+
+ fixture.nativeElement.querySelector('button').dispatchEvent(new MouseEvent('click'));
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(api.favoritesApi.removeFavoriteSite).toHaveBeenCalled();
+ });
+});
diff --git a/lib/core/directives/library-favorite.directive.ts b/lib/core/directives/library-favorite.directive.ts
new file mode 100644
index 0000000000..f511e3014a
--- /dev/null
+++ b/lib/core/directives/library-favorite.directive.ts
@@ -0,0 +1,107 @@
+/*!
+ * @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 { Directive, HostListener, Input, OnChanges, Output, EventEmitter } from '@angular/core';
+import { SiteBody, FavoriteBody, FavoriteEntry, Site } from '@alfresco/js-api';
+import { AlfrescoApiService } from '../services/alfresco-api.service';
+
+export interface LibraryEntity {
+ entry: Site;
+ isLibrary: boolean;
+ isFavorite: boolean;
+}
+
+@Directive({
+ selector: '[adf-favorite-library]',
+ exportAs: 'favoriteLibrary'
+})
+export class LibraryFavoriteDirective implements OnChanges {
+ @Input('adf-favorite-library')
+ library: LibraryEntity = null;
+
+ @Output() toggle = new EventEmitter();
+ // tslint:disable-next-line: no-output-native
+ @Output() error = new EventEmitter();
+
+ private targetLibrary = null;
+
+ @HostListener('click')
+ onClick() {
+ const guid = this.targetLibrary.entry.guid;
+
+ if (this.targetLibrary.isFavorite) {
+ this.removeFavorite(guid);
+ } else {
+ this.addFavorite({
+ target: {
+ site: {
+ guid
+ }
+ }
+ });
+ }
+ }
+
+ constructor(private alfrescoApiService: AlfrescoApiService) {}
+
+ ngOnChanges(changes) {
+ if (!changes.library.currentValue) {
+ this.targetLibrary = null;
+ return;
+ }
+
+ this.targetLibrary = changes.library.currentValue;
+ this.markFavoriteLibrary(changes.library.currentValue);
+ }
+
+ isFavorite(): boolean {
+ return this.targetLibrary && this.targetLibrary.isFavorite;
+ }
+
+ private async markFavoriteLibrary(library: LibraryEntity) {
+ if (this.targetLibrary.isFavorite === undefined) {
+ try {
+ await this.alfrescoApiService.peopleApi.getFavoriteSite('-me-', library.entry.id);
+ this.targetLibrary.isFavorite = true;
+ } catch {
+ this.targetLibrary.isFavorite = false;
+ }
+ } else {
+ this.targetLibrary = library;
+ }
+ }
+
+ private addFavorite(favoriteBody: FavoriteBody) {
+ this.alfrescoApiService.peopleApi
+ .addFavorite('-me-', favoriteBody)
+ .then((libraryEntry: FavoriteEntry) => {
+ this.targetLibrary.isFavorite = true;
+ this.toggle.emit(libraryEntry);
+ })
+ .catch((error) => this.error.emit(error));
+ }
+
+ private removeFavorite(favoriteId: string) {
+ this.alfrescoApiService.favoritesApi
+ .removeFavoriteSite('-me-', favoriteId)
+ .then((libraryBody: SiteBody) => {
+ this.targetLibrary.isFavorite = false;
+ this.toggle.emit(libraryBody);
+ })
+ .catch((error) => this.error.emit(error));
+ }
+}
diff --git a/lib/core/directives/library-membership.directive.spec.ts b/lib/core/directives/library-membership.directive.spec.ts
new file mode 100644
index 0000000000..0a47982556
--- /dev/null
+++ b/lib/core/directives/library-membership.directive.spec.ts
@@ -0,0 +1,220 @@
+/*!
+ * @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 { fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { AlfrescoApiService, SitesService } from '../services';
+import { LibraryMembershipDirective } from './library-membership.directive';
+import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core';
+import { of, throwError, Subject } from 'rxjs';
+import { TranslateModule } from '@ngx-translate/core';
+import { DirectiveModule } from './directive.module';
+import { CoreModule } from '../core.module';
+import { AlfrescoApiServiceMock } from '../mock/alfresco-api.service.mock';
+
+describe('LibraryMembershipDirective', () => {
+ let alfrescoApiService: AlfrescoApiService;
+ let directive: LibraryMembershipDirective;
+ let peopleApi: any;
+ let sitesService: SitesService;
+ let addMembershipSpy: jasmine.Spy;
+ let getMembershipSpy: jasmine.Spy;
+ let deleteMembershipSpy: jasmine.Spy;
+ let mockSupportedVersion = false;
+
+ let testSiteEntry: any;
+ let requestedMembershipResponse: any;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [TranslateModule.forRoot(), DirectiveModule, CoreModule.forRoot()],
+ providers: [
+ { provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ });
+
+ testSiteEntry = {
+ id: 'id-1',
+ guid: 'site-1',
+ title: 'aa t m',
+ visibility: 'MODERATED'
+ };
+
+ requestedMembershipResponse = {
+ id: testSiteEntry.id,
+ createdAt: '2018-11-14',
+ site: testSiteEntry
+ };
+
+ alfrescoApiService = TestBed.inject(AlfrescoApiService);
+ sitesService = TestBed.inject(SitesService);
+ peopleApi = alfrescoApiService.getInstance().core.peopleApi;
+ directive = new LibraryMembershipDirective(alfrescoApiService, sitesService, {
+ ecmProductInfo$: new Subject(),
+ isVersionSupported: () => mockSupportedVersion
+ } as any);
+ });
+
+ describe('markMembershipRequest', () => {
+ beforeEach(() => {
+ getMembershipSpy = spyOn(peopleApi, 'getSiteMembershipRequest').and.returnValue(Promise.resolve({ entry: requestedMembershipResponse }));
+ });
+
+ it('should not check membership requests if no entry is selected', fakeAsync(() => {
+ const selection = {};
+ const change = new SimpleChange(null, selection, true);
+ directive.ngOnChanges({ selection: change });
+ tick();
+ expect(getMembershipSpy).not.toHaveBeenCalled();
+ }));
+
+ it('should check if a membership request exists for the selected library', fakeAsync(() => {
+ const selection = { entry: {} };
+ const change = new SimpleChange(null, selection, true);
+ directive.ngOnChanges({ selection: change });
+ tick();
+ expect(getMembershipSpy.calls.count()).toBe(1);
+ }));
+
+ it('should remember when a membership request exists for selected library', fakeAsync(() => {
+ const selection = { entry: testSiteEntry };
+ const change = new SimpleChange(null, selection, true);
+ directive.ngOnChanges({ selection: change });
+ tick();
+ expect(directive.targetSite.joinRequested).toBe(true);
+ }));
+
+ it('should remember when a membership request is not found for selected library', fakeAsync(() => {
+ getMembershipSpy.and.returnValue(Promise.reject());
+
+ const selection = { entry: testSiteEntry };
+ const change = new SimpleChange(null, selection, true);
+ directive.ngOnChanges({ selection: change });
+ tick();
+ expect(directive.targetSite.joinRequested).toBe(false);
+ }));
+ });
+
+ describe('toggleMembershipRequest', () => {
+ beforeEach(() => {
+ mockSupportedVersion = false;
+ getMembershipSpy = spyOn(peopleApi, 'getSiteMembershipRequest').and.returnValue(Promise.resolve({ entry: requestedMembershipResponse }));
+ addMembershipSpy = spyOn(peopleApi, 'addSiteMembershipRequest').and.returnValue(Promise.resolve({ entry: requestedMembershipResponse }));
+ deleteMembershipSpy = spyOn(peopleApi, 'removeSiteMembershipRequest').and.returnValue(Promise.resolve({}));
+ });
+
+ it('should do nothing if there is no selected library ', fakeAsync(() => {
+ const selection = {};
+ const change = new SimpleChange(null, selection, true);
+ directive.ngOnChanges({ selection: change });
+ tick();
+ directive.toggleMembershipRequest();
+ tick();
+ expect(addMembershipSpy).not.toHaveBeenCalled();
+ expect(deleteMembershipSpy).not.toHaveBeenCalled();
+ }));
+
+ it('should delete membership request if there is one', fakeAsync(() => {
+ const selection = { entry: testSiteEntry };
+ const change = new SimpleChange(null, selection, true);
+ directive.ngOnChanges({ selection: change });
+ tick();
+ directive.toggleMembershipRequest();
+ tick();
+ expect(deleteMembershipSpy).toHaveBeenCalled();
+ }));
+
+ it('should call API to make a membership request if there is none', fakeAsync(() => {
+ const selection = { entry: { id: 'no-membership-requested' } };
+ const change = new SimpleChange(null, selection, true);
+ directive.ngOnChanges({ selection: change });
+ tick();
+ directive.toggleMembershipRequest();
+ tick();
+ expect(addMembershipSpy).toHaveBeenCalledWith('-me-', { id: 'no-membership-requested' });
+ expect(deleteMembershipSpy).not.toHaveBeenCalled();
+ }));
+
+ it("should add 'workspace' to send appropriate email", fakeAsync(() => {
+ mockSupportedVersion = true;
+ const selection = { entry: { id: 'no-membership-requested' } };
+ const change = new SimpleChange(null, selection, true);
+ directive.ngOnChanges({ selection: change });
+ tick();
+ directive.toggleMembershipRequest();
+ tick();
+ expect(addMembershipSpy).toHaveBeenCalledWith('-me-', { id: 'no-membership-requested', client: 'workspace' });
+ expect(deleteMembershipSpy).not.toHaveBeenCalled();
+ }));
+
+ it('should call API to add user to library if admin user', fakeAsync(() => {
+ const createSiteMembershipSpy = spyOn(sitesService, 'createSiteMembership').and.returnValue(of({} as any));
+ const selection = { entry: { id: 'no-membership-requested' } };
+ const selectionChange = new SimpleChange(null, selection, true);
+ directive.isAdmin = true;
+ directive.ngOnChanges({ selection: selectionChange });
+ tick();
+ directive.toggleMembershipRequest();
+ tick();
+ expect(createSiteMembershipSpy).toHaveBeenCalled();
+ expect(addMembershipSpy).not.toHaveBeenCalled();
+ }));
+
+ it('should emit error when the request to join a library fails', fakeAsync(() => {
+ spyOn(directive.error, 'emit');
+ addMembershipSpy.and.returnValue(throwError('err'));
+
+ const selection = { entry: { id: 'no-membership-requested' } };
+ const change = new SimpleChange(null, selection, true);
+ directive.ngOnChanges({ selection: change });
+ tick();
+ directive.toggleMembershipRequest();
+ tick();
+ expect(directive.error.emit).toHaveBeenCalled();
+ }));
+
+ it('should emit specific error message on invalid email address server error', fakeAsync(() => {
+ const emitErrorSpy = spyOn(directive.error, 'emit');
+ const selection = { entry: { id: 'no-membership-requested' } };
+ const change = new SimpleChange(null, selection, true);
+ directive.ngOnChanges({ selection: change });
+ tick();
+
+ const testData = [
+ {
+ fixture: 'Failed to resolve sender mail address',
+ expected: 'APP.MESSAGES.ERRORS.INVALID_SENDER_EMAIL'
+ },
+ {
+ fixture: 'All recipients for the mail action were invalid',
+ expected: 'APP.MESSAGES.ERRORS.INVALID_RECEIVER_EMAIL'
+ }
+ ];
+
+ testData.forEach((data) => {
+ addMembershipSpy.and.returnValue(throwError({ message: data.fixture }));
+ emitErrorSpy.calls.reset();
+ directive.toggleMembershipRequest();
+ tick();
+ expect(emitErrorSpy).toHaveBeenCalledWith({
+ error: { message: data.fixture },
+ i18nKey: data.expected
+ });
+ });
+ }));
+ });
+});
diff --git a/lib/core/directives/library-membership.directive.ts b/lib/core/directives/library-membership.directive.ts
new file mode 100644
index 0000000000..59c4ba88e2
--- /dev/null
+++ b/lib/core/directives/library-membership.directive.ts
@@ -0,0 +1,229 @@
+/*!
+ * @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 { Directive, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
+import { SiteEntry, SiteMembershipRequestBody, SiteMemberEntry, SiteMembershipRequestEntry } from '@alfresco/js-api';
+import { BehaviorSubject, from, Observable } from 'rxjs';
+import { AlfrescoApiService } from '../services/alfresco-api.service';
+import { SitesService } from '../services/sites.service';
+import { VersionCompatibilityService } from '../services/version-compatibility.service';
+
+export interface LibraryMembershipToggleEvent {
+ updatedEntry?: any;
+ shouldReload: boolean;
+ i18nKey: string;
+}
+
+export interface LibraryMembershipErrorEvent {
+ error: any;
+ i18nKey: string;
+}
+
+@Directive({
+ selector: '[adf-library-membership]',
+ exportAs: 'libraryMembership'
+})
+export class LibraryMembershipDirective implements OnChanges {
+ targetSite: any = null;
+
+ isJoinRequested: BehaviorSubject = new BehaviorSubject(false);
+
+ /** Site for which to toggle the membership request. */
+ @Input('adf-library-membership')
+ selection: SiteEntry = null;
+
+ /** Site for which to toggle the membership request. */
+ @Input()
+ isAdmin = false;
+
+ @Output()
+ toggle = new EventEmitter();
+
+ // tslint:disable-next-line: no-output-native
+ @Output()
+ error = new EventEmitter();
+
+ @HostListener('click')
+ onClick() {
+ this.toggleMembershipRequest();
+ }
+
+ constructor(
+ private alfrescoApiService: AlfrescoApiService,
+ private sitesService: SitesService,
+ private versionCompatibilityService: VersionCompatibilityService
+ ) {}
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (!changes.selection.currentValue || !changes.selection.currentValue.entry) {
+ this.targetSite = null;
+
+ return;
+ }
+ this.targetSite = changes.selection.currentValue.entry;
+ this.markMembershipRequest();
+ }
+
+ toggleMembershipRequest() {
+ if (!this.targetSite) {
+ return;
+ }
+
+ if (this.targetSite.joinRequested) {
+ this.cancelJoinRequest().subscribe(
+ () => {
+ this.targetSite.joinRequested = false;
+ this.isJoinRequested.next(false);
+ const info = {
+ updatedEntry: this.targetSite,
+ shouldReload: false,
+ i18nKey: 'APP.MESSAGES.INFO.JOIN_CANCELED'
+ };
+ this.toggle.emit(info);
+ },
+ (error) => {
+ const errWithMessage = {
+ error,
+ i18nKey: 'APP.MESSAGES.ERRORS.JOIN_CANCEL_FAILED'
+ };
+ this.error.emit(errWithMessage);
+ }
+ );
+ }
+
+ if (!this.targetSite.joinRequested && !this.isAdmin) {
+ this.joinLibraryRequest().subscribe(
+ (createdMembership) => {
+ this.targetSite.joinRequested = true;
+ this.isJoinRequested.next(true);
+
+ if (createdMembership.entry && createdMembership.entry.site && createdMembership.entry.site.role) {
+ const info = {
+ shouldReload: true,
+ i18nKey: 'APP.MESSAGES.INFO.JOINED'
+ };
+ this.toggle.emit(info);
+ } else {
+ const info = {
+ updatedEntry: this.targetSite,
+ shouldReload: false,
+ i18nKey: 'APP.MESSAGES.INFO.JOIN_REQUESTED'
+ };
+ this.toggle.emit(info);
+ }
+ },
+ (error) => {
+ const errWithMessage = {
+ error,
+ i18nKey: 'APP.MESSAGES.ERRORS.JOIN_REQUEST_FAILED'
+ };
+
+ const senderEmailCheck = 'Failed to resolve sender mail address';
+ const receiverEmailCheck = 'All recipients for the mail action were invalid';
+
+ if (error.message) {
+ if (error.message.includes(senderEmailCheck)) {
+ errWithMessage.i18nKey = 'APP.MESSAGES.ERRORS.INVALID_SENDER_EMAIL';
+ } else if (error.message.includes(receiverEmailCheck)) {
+ errWithMessage.i18nKey = 'APP.MESSAGES.ERRORS.INVALID_RECEIVER_EMAIL';
+ }
+ }
+
+ this.error.emit(errWithMessage);
+ }
+ );
+ }
+
+ if (this.isAdmin) {
+ this.joinLibrary().subscribe(
+ (createdMembership: SiteMemberEntry) => {
+ if (createdMembership.entry && createdMembership.entry.role) {
+ const info = {
+ shouldReload: true,
+ i18nKey: 'APP.MESSAGES.INFO.JOINED'
+ };
+ this.toggle.emit(info);
+ }
+ },
+ (error) => {
+ const errWithMessage = {
+ error,
+ i18nKey: 'APP.MESSAGES.ERRORS.JOIN_REQUEST_FAILED'
+ };
+
+ const senderEmailCheck = 'Failed to resolve sender mail address';
+ const receiverEmailCheck = 'All recipients for the mail action were invalid';
+
+ if (error.message) {
+ if (error.message.includes(senderEmailCheck)) {
+ errWithMessage.i18nKey = 'APP.MESSAGES.ERRORS.INVALID_SENDER_EMAIL';
+ } else if (error.message.includes(receiverEmailCheck)) {
+ errWithMessage.i18nKey = 'APP.MESSAGES.ERRORS.INVALID_RECEIVER_EMAIL';
+ }
+ }
+
+ this.error.emit(errWithMessage);
+ }
+ );
+ }
+ }
+
+ markMembershipRequest() {
+ if (!this.targetSite) {
+ return;
+ }
+
+ this.getMembershipRequest().subscribe(
+ (data) => {
+ if (data.entry.id === this.targetSite.id) {
+ this.targetSite.joinRequested = true;
+ this.isJoinRequested.next(true);
+ }
+ },
+ () => {
+ this.targetSite.joinRequested = false;
+ this.isJoinRequested.next(false);
+ }
+ );
+ }
+
+ private joinLibraryRequest(): Observable {
+ const memberBody = {
+ id: this.targetSite.id
+ } as SiteMembershipRequestBody;
+
+ if (this.versionCompatibilityService.isVersionSupported('7.0.0')) {
+ memberBody.client = 'workspace';
+ }
+ return from(this.alfrescoApiService.peopleApi.addSiteMembershipRequest('-me-', memberBody));
+ }
+
+ private joinLibrary() {
+ return this.sitesService.createSiteMembership(this.targetSite.id, {
+ role: 'SiteConsumer',
+ id: '-me-'
+ });
+ }
+
+ private cancelJoinRequest() {
+ return from(this.alfrescoApiService.peopleApi.removeSiteMembershipRequest('-me-', this.targetSite.id));
+ }
+
+ private getMembershipRequest() {
+ return from(this.alfrescoApiService.peopleApi.getSiteMembershipRequest('-me-', this.targetSite.id));
+ }
+}
diff --git a/lib/core/directives/public-api.ts b/lib/core/directives/public-api.ts
index 9010e85972..cd412ca945 100644
--- a/lib/core/directives/public-api.ts
+++ b/lib/core/directives/public-api.ts
@@ -26,5 +26,7 @@ export * from './upload.directive';
export * from './version-compatibility.directive';
export * from './tooltip-card/tooltip-card.directive';
export * from './infinite-select-scroll.directive';
+export * from './library-favorite.directive';
+export * from './library-membership.directive';
export * from './directive.module';