mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
85123: WIP: Support for theme-specific head tags
This commit is contained in:
@@ -31,12 +31,12 @@ import { AuthService } from './core/auth/auth.service';
|
|||||||
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
|
||||||
import { MenuService } from './shared/menu/menu.service';
|
import { MenuService } from './shared/menu/menu.service';
|
||||||
import { HostWindowService } from './shared/host-window.service';
|
import { HostWindowService } from './shared/host-window.service';
|
||||||
import { ThemeConfig } from '../config/theme.model';
|
import {HeadTagConfig, ThemeConfig} from '../config/theme.model';
|
||||||
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
|
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { models } from './core/core.module';
|
import { models } from './core/core.module';
|
||||||
import { LocaleService } from './core/locale/locale.service';
|
import { LocaleService } from './core/locale/locale.service';
|
||||||
import { hasValue, isNotEmpty } from './shared/empty.util';
|
import { hasValue, isEmpty, isNotEmpty } from './shared/empty.util';
|
||||||
import { KlaroService } from './shared/cookies/klaro.service';
|
import { KlaroService } from './shared/cookies/klaro.service';
|
||||||
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
||||||
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
||||||
@@ -115,11 +115,11 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
this.isThemeCSSLoading$.next(true);
|
this.isThemeCSSLoading$.next(true);
|
||||||
}
|
}
|
||||||
if (hasValue(themeName)) {
|
if (hasValue(themeName)) {
|
||||||
this.setThemeCss(themeName);
|
this.loadGlobalThemeConfig(themeName);
|
||||||
} else if (hasValue(DEFAULT_THEME_CONFIG)) {
|
} else if (hasValue(DEFAULT_THEME_CONFIG)) {
|
||||||
this.setThemeCss(DEFAULT_THEME_CONFIG.name);
|
this.loadGlobalThemeConfig(DEFAULT_THEME_CONFIG.name);
|
||||||
} else {
|
} else {
|
||||||
this.setThemeCss(BASE_THEME_NAME);
|
this.loadGlobalThemeConfig(BASE_THEME_NAME);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -233,6 +233,11 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadGlobalThemeConfig(themeName: string): void {
|
||||||
|
this.setThemeCss(themeName);
|
||||||
|
this.setHeadTags(themeName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the theme css file in <head>
|
* Update the theme css file in <head>
|
||||||
*
|
*
|
||||||
@@ -243,7 +248,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
const head = this.document.getElementsByTagName('head')[0];
|
const head = this.document.getElementsByTagName('head')[0];
|
||||||
// Array.from to ensure we end up with an array, not an HTMLCollection, which would be
|
// Array.from to ensure we end up with an array, not an HTMLCollection, which would be
|
||||||
// automatically updated if we add nodes later
|
// automatically updated if we add nodes later
|
||||||
const currentThemeLinks = Array.from(this.document.getElementsByClassName('theme-css'));
|
const currentThemeLinks = Array.from(head.getElementsByClassName('theme-css'));
|
||||||
const link = this.document.createElement('link');
|
const link = this.document.createElement('link');
|
||||||
link.setAttribute('rel', 'stylesheet');
|
link.setAttribute('rel', 'stylesheet');
|
||||||
link.setAttribute('type', 'text/css');
|
link.setAttribute('type', 'text/css');
|
||||||
@@ -265,6 +270,51 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
head.appendChild(link);
|
head.appendChild(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setHeadTags(themeName: string): void {
|
||||||
|
const head = this.document.getElementsByTagName('head')[0];
|
||||||
|
|
||||||
|
// clear head tags
|
||||||
|
const currentHeadTags = Array.from(head.getElementsByClassName('theme-head-tag'));
|
||||||
|
if (isNotEmpty(currentHeadTags)) {
|
||||||
|
currentHeadTags.forEach((currentHeadTag: any) => currentHeadTag.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new head tags (not yet added to DOM)
|
||||||
|
const headTagFragment = document.createDocumentFragment();
|
||||||
|
this.createHeadTags(themeName)
|
||||||
|
.forEach(newHeadTag => headTagFragment.appendChild(newHeadTag));
|
||||||
|
|
||||||
|
// add new head tags to DOM
|
||||||
|
head.appendChild(headTagFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createHeadTags(themeName: string): HTMLElement[] {
|
||||||
|
const themeConfig = this.themeService.getThemeConfigFor(themeName);
|
||||||
|
const headTagConfigs = themeConfig?.headTags;
|
||||||
|
|
||||||
|
// if the current theme does not have head tags, we inherit the head tags of the parent
|
||||||
|
if (isEmpty(headTagConfigs)) {
|
||||||
|
const parentThemeName = themeConfig.extends;
|
||||||
|
return isNotEmpty(parentThemeName) ? this.createHeadTags(parentThemeName) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return headTagConfigs.map(this.createHeadTag.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private createHeadTag(themeHeadTag: HeadTagConfig): HTMLElement {
|
||||||
|
const tag = this.document.createElement(themeHeadTag.tagName);
|
||||||
|
|
||||||
|
if (isNotEmpty(themeHeadTag.attributes)) {
|
||||||
|
Object.entries(themeHeadTag.attributes)
|
||||||
|
.forEach(([key, value]) => tag.setAttribute(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'class' attribute should always be 'theme-head-tag' for removal
|
||||||
|
tag.setAttribute('class', 'theme-head-tag');
|
||||||
|
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
private trackIdleModal() {
|
private trackIdleModal() {
|
||||||
const isIdle$ = this.authService.isUserIdle();
|
const isIdle$ = this.authService.isUserIdle();
|
||||||
const isAuthenticated$ = this.authService.isAuthenticated();
|
const isAuthenticated$ = this.authService.isAuthenticated();
|
||||||
|
@@ -12,6 +12,28 @@ export interface NamedThemeConfig extends Config {
|
|||||||
* its ancestor theme(s) will be checked recursively before falling back to the default theme.
|
* its ancestor theme(s) will be checked recursively before falling back to the default theme.
|
||||||
*/
|
*/
|
||||||
extends?: string;
|
extends?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of HTML tags that should be added to the HEAD section of the document, whenever this theme is active.
|
||||||
|
*/
|
||||||
|
headTags?: HeadTagConfig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that represents a single theme-specific HTML tag in the HEAD section of the page.
|
||||||
|
*/
|
||||||
|
export interface HeadTagConfig extends Config {
|
||||||
|
/**
|
||||||
|
* The name of the HTML tag
|
||||||
|
*/
|
||||||
|
tagName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes on the HTML tag
|
||||||
|
*/
|
||||||
|
attributes?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegExThemeConfig extends NamedThemeConfig {
|
export interface RegExThemeConfig extends NamedThemeConfig {
|
||||||
|
@@ -292,7 +292,39 @@ export const environment: GlobalConfig = {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// The default dspace theme
|
// The default dspace theme
|
||||||
name: 'dspace'
|
name: 'dspace',
|
||||||
|
headTags: [
|
||||||
|
{
|
||||||
|
tagName: 'link',
|
||||||
|
attributes: {
|
||||||
|
'rel': 'icon',
|
||||||
|
'href': 'assets/dspace/images/favicons/favicon.ico',
|
||||||
|
'sizes': 'any',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tagName: 'link',
|
||||||
|
attributes: {
|
||||||
|
'rel': 'icon',
|
||||||
|
'href': 'assets/dspace/images/favicons/favicon.svg',
|
||||||
|
'type': 'image/svg+xml',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tagName: 'link',
|
||||||
|
attributes: {
|
||||||
|
'rel': 'apple-touch-icon',
|
||||||
|
'href': 'assets/dspace/images/favicons/apple-touch-icon.png',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tagName: 'link',
|
||||||
|
attributes: {
|
||||||
|
'rel': 'manifest',
|
||||||
|
'href': 'assets/dspace/images/favicons/manifest.webmanifest',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with "image" or "video").
|
// Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with "image" or "video").
|
||||||
|
Reference in New Issue
Block a user