Skip to content
Merged
19 changes: 19 additions & 0 deletions config/config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -736,3 +736,22 @@ searchResults:
followAuthorityMetadataValuesLimit: 5


# Configuration of social links using AddToAny plugin
addToAnyPlugin:
# This is enabled flag
socialNetworksEnabled: false
# If you want to self-host check https://www.addtoany.com/buttons/customize/host_cache
scriptUrl: "https://static.addtoany.com/menu/page.js"
# Check available integrations, visit https://www.addtoany.com/buttons/for/website
# 1. Click "Choose Services", select integrations, then click Done
# 2. Get Button Code
# 3. You will get a HTML e.g. <a class="a2a_button_facebook"></a> where "facebook" is part you want to include in list
buttons:
- facebook
- twitter
- linkedin
- email
- copy_link
showPlusButton: true
showCounters: true
title: DSpace demo
4 changes: 4 additions & 0 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<ds-root
[shouldShowFullscreenLoader]="(isAuthBlocking$ | async) || (isThemeLoading$ | async)"
[shouldShowRouteLoader]="isRouteLoading$ | async"></ds-root>

@if (browserPlatform) {
<ds-social></ds-social>
}
14 changes: 13 additions & 1 deletion src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
import { CSSVariableService } from './shared/sass-helper/css-variable.service';
import { HostWindowState } from './shared/search/host-window.reducer';
import { ThemeService } from './shared/theme-support/theme.service';
import { SocialComponent } from './social/social.component';
import { SocialService } from './social/social.service';

@Component({
selector: 'ds-app',
Expand All @@ -60,6 +62,7 @@ import { ThemeService } from './shared/theme-support/theme.service';
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncPipe,
SocialComponent,
ThemedRootComponent,
],
})
Expand Down Expand Up @@ -87,6 +90,11 @@ export class AppComponent implements OnInit, AfterViewInit {
*/
idleModalOpen: boolean;

/**
* In order to show sharing component only in csr
*/
browserPlatform = false;

constructor(
@Inject(NativeWindowService) private _window: NativeWindowRef,
@Inject(DOCUMENT) private document: any,
Expand All @@ -99,16 +107,20 @@ export class AppComponent implements OnInit, AfterViewInit {
private cssService: CSSVariableService,
private modalService: NgbModal,
private modalConfig: NgbModalConfig,
private socialService: SocialService,
) {
this.notificationOptions = environment.notifications;
this.browserPlatform = isPlatformBrowser(this.platformId);

if (isPlatformBrowser(this.platformId)) {
if (this.browserPlatform) {
this.trackIdleModal();
}

this.isThemeLoading$ = this.themeService.isThemeLoading$;

this.storeCSSVariables();

this.socialService.initialize();
}

ngOnInit() {
Expand Down
1 change: 1 addition & 0 deletions src/app/home-page/home-page-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const ROUTES: Route[] = [
pathMatch: 'full',
data: {
title: 'home.title',
showSocialButtons: true,
menu: {
public: [{
id: 'statistics_site',
Expand Down
3 changes: 3 additions & 0 deletions src/app/item-page/item-page-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ export const ROUTES: Route[] = [
},
{
path: ':id',
data: {
showSocialButtons: true,
},
resolve: {
dso: itemPageResolver,
itemRequest: accessTokenResolver,
Expand Down
3 changes: 2 additions & 1 deletion src/app/search-page/search-page-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { ThemedSearchPageComponent } from './themed-search-page.component';

export const ROUTES: Route[] = [{
path: '',
resolve: { breadcrumb: i18nBreadcrumbResolver }, data: { title: 'search.title', breadcrumbKey: 'search' },
resolve: { breadcrumb: i18nBreadcrumbResolver },
data: { title: 'search.title', breadcrumbKey: 'search', showSocialButtons: true },
children: [
{ path: '', component: ThemedSearchPageComponent },
{
Expand Down
9 changes: 9 additions & 0 deletions src/app/social/social.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div id="dspace-a2a" [class.d-none]="(showOnCurrentRoute$ | async) !== true" data-a2a-scroll-show="0,60"
class="a2a_kit a2a_kit_size_32 a2a_floating_style a2a_default_style" [attr.data-a2a-title]="title" [attr.data-a2a-url]="url">
@for (button of buttonList; track button) {
<a [class]="'a2a_button_' + button" [class.a2a_counter]="showCounters && button !== 'facebook'"></a>
}
@if (showPlusButton) {
<a class="a2a_dd" href="https://www.addtoany.com/share"></a>
}
</div>
7 changes: 7 additions & 0 deletions src/app/social/social.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
::ng-deep {
div#dspace-a2a {
bottom: 0;
right: 0;
z-index: 998;
}
}
39 changes: 39 additions & 0 deletions src/app/social/social.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { DOCUMENT } from '@angular/common';
import {
ComponentFixture,
TestBed,
} from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { StoreModule } from '@ngrx/store';

import { SocialComponent } from './social.component';
import { SocialService } from './social.service';

describe('SocialComponent', () => {
let component: SocialComponent;
let fixture: ComponentFixture<SocialComponent>;
let document: Document;

const socialServiceStub = {};

const activatedRouteStub = {} as ActivatedRoute;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [StoreModule.forRoot({}), SocialComponent],
providers: [
{ provide: ActivatedRoute, useValue: activatedRouteStub },
{ provide: SocialService, useValue: socialServiceStub },
],
}).compileComponents();
fixture = TestBed.createComponent(SocialComponent);
component = fixture.componentInstance;
document = TestBed.inject(DOCUMENT) as any;
fixture.detectChanges();
});

it('should create socialComponent', () => {
expect(component).toBeTruthy();
});

});
53 changes: 53 additions & 0 deletions src/app/social/social.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { AsyncPipe } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
OnInit,
} from '@angular/core';
import { Observable } from 'rxjs';

import { SocialService } from './social.service';

@Component({
selector: 'ds-social',
templateUrl: './social.component.html',
styleUrls: ['./social.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncPipe,
],
})
/**
* Component to render dynamically the social2 buttons using addToAny plugin
*/
export class SocialComponent implements OnInit {

/**
* The script containing the profile ID
*/
script: HTMLScriptElement;

showOnCurrentRoute$: Observable<boolean>;

buttonList: string[];
showPlusButton: boolean;
title: string;
url: string;
showCounters: boolean;

constructor(
private socialService: SocialService,
) {}

ngOnInit() {
if (this.socialService.enabled) {
this.buttonList = this.socialService.configuration.buttons;
this.showPlusButton = this.socialService.configuration.showPlusButton;
this.showCounters = this.socialService.configuration.showCounters;
this.title = this.socialService.configuration.title;
this.socialService.initializeAddToAnyScript();
this.showOnCurrentRoute$ = this.socialService.showOnCurrentRoute$;
}
}

}
119 changes: 119 additions & 0 deletions src/app/social/social.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import {
DOCUMENT,
isPlatformBrowser,
} from '@angular/common';
import {
Inject,
Injectable,
PLATFORM_ID,
} from '@angular/core';
import {
ChildActivationEnd,
Router,
} from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import {
distinctUntilChanged,
filter,
map,
} from 'rxjs/operators';

import { environment } from '../../environments/environment';

/**
* Singleton service responsible for integration with AddToAny plugin to initialize 3rd party script and hold state
* Bootstrap with Angular start in {@link AppComponent} which initializess route subscription
* Bootstrap of integration starts at clientside with {@link SocialComponent} which loads 3rd party script
*/
@Injectable( { providedIn: 'root' } )
export class SocialService {
Comment thread
pzlakowski marked this conversation as resolved.
private showOnCurrentRouteSubject = new BehaviorSubject(false);

/**
* Show/hide the social buttons according to the activated route
*/
showOnCurrentRoute$ = this.showOnCurrentRouteSubject.asObservable();

private readonly isSocialEnabled: boolean;

constructor(
@Inject(PLATFORM_ID) protected platformId: any,
@Inject(DOCUMENT) private _document: Document,
private router: Router,
) {
this.isSocialEnabled = isPlatformBrowser(this.platformId) && environment.addToAnyPlugin.socialNetworksEnabled;
}

/**
* Traverse tree from bottom to parent in route definition to get whenever feature is enabled via *showSocialButtons*
*/
private activatedRouteDataChanges$ = this.router.events.pipe(
filter(events => events instanceof ChildActivationEnd),
map((event: ChildActivationEnd) => event.snapshot),
map(route => {
while (route.firstChild) {
route = route.firstChild;
}
return route;
}),
filter(route => route.outlet === 'primary'),
map(route => route.data),
);

/**
* Returns whether the social network buttons are enabled
*/
get enabled() {
return this.isSocialEnabled;
}

/**
* Returns the list of available buttons
*/
get configuration() {
return environment.addToAnyPlugin;
}

/**
* Import the AddToAny JavaScript
*/
initializeAddToAnyScript(): any {
// Initializing the addToAny script
const script = this._document.createElement('script');
script.type = 'text/javascript';
script.src = environment.addToAnyPlugin.scriptUrl;
script.async = true;

// Wait for document to finish grow vertically so that script listener handles properly body height changes
let lastBodyHeight = 0;
const documentBody = this._document.body;

const bodyHeightInterval = setInterval(() => {
const currentBodyHeight = documentBody.getBoundingClientRect().height;

if (currentBodyHeight > lastBodyHeight) {
lastBodyHeight = currentBodyHeight;
} else {
this._document.head.appendChild(script);
clearInterval(bodyHeightInterval);
}
}, 200);
}

/**
* Initialize the Social service. This method must be called only inside app component.
*/
initialize() {
if (!this.enabled) {
return;
}

const showSocialButtons = this.activatedRouteDataChanges$.pipe(
map(data => data?.showSocialButtons === true),
distinctUntilChanged(),
);

showSocialButtons.subscribe(this.showOnCurrentRouteSubject);
}

}
32 changes: 32 additions & 0 deletions src/config/add-to-any-plugin-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Config } from './config.interface';

/**
* Configuration interface for AddToAny plugin to display social links
*/
export interface AddToAnyPluginConfig extends Config {
Comment thread
pzlakowski marked this conversation as resolved.
/**
* Script URL to AddToAny plugin, defaults to AddToAny CDN
*/
scriptUrl: string;
/**
* Enable feature flag
*/
socialNetworksEnabled: boolean;
/**
* Mandatory parameter for AddToAny(data-a2a-title). It will be included as part of social link share
*/
title: string;
/**
* Listed elements will be rendered with their respective icon
* List of the integrations, you can check these at https://www.addtoany.com/buttons/for/website
*/
buttons: string[];
/**
* Shows plus button which allows to share with different integrations which were not listed in buttons
*/
showPlusButton: boolean;
/**
* Shows counter for the integration https://www.addtoany.com/buttons/customize/share_counters where it is allowed
*/
showCounters: boolean;
}
2 changes: 2 additions & 0 deletions src/config/app-config.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {

import { AccessibilitySettingsConfig } from './accessibility-settings.config';
import { ActuatorsConfig } from './actuators.config';
import { AddToAnyPluginConfig } from './add-to-any-plugin-config';
import { AdminNotifyMetricsRow } from './admin-notify-metrics.config';
import { AuthConfig } from './auth-config.interfaces';
import { BrowseByConfig } from './browse-by-config.interface';
Expand Down Expand Up @@ -73,6 +74,7 @@ interface AppConfig extends Config {
accessibility: AccessibilitySettingsConfig;
layout: LayoutConfig;
searchResult: SearchResultConfig;
addToAnyPlugin: AddToAnyPluginConfig;
}

/**
Expand Down
Loading
Loading