-
Notifications
You must be signed in to change notification settings - Fork 467
/
favicons.ts
167 lines (108 loc) · 4.06 KB
/
favicons.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Import the core angular services.
import { Inject } from "@angular/core";
import { InjectionToken } from "@angular/core";
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
export interface FaviconsConfig {
icons: IconsConfig;
cacheBusting?: boolean;
}
export interface IconsConfig {
[ name: string ]: IconConfig;
}
export interface IconConfig {
type: string;
href: string;
isDefault?: boolean;
}
export var BROWSER_FAVICONS_CONFIG = new InjectionToken<FaviconsConfig>( "Favicons Configuration" );
// This abstract class acts as both the interface for implementation (for any developer
// that wants to create an alternate implementation) and as the dependency-injection
// token that the rest of the application can use.
export abstract class Favicons {
abstract activate( name: string ) : void;
abstract reset() : void;
}
// I provide the browser-oriented implementation of the Favicons class.
export class BrowserFavicons implements Favicons {
private elementId: string;
private icons: IconsConfig;
private useCacheBusting: boolean;
// I initialize the Favicons service.
constructor( @Inject( BROWSER_FAVICONS_CONFIG ) config: FaviconsConfig ) {
this.elementId = "favicons-service-injected-node";
this.icons = Object.assign( Object.create( null ), config.icons );
this.useCacheBusting = ( config.cacheBusting || false );
// Since the document may have a static favicon definition, we want to strip out
// any exisitng elements that are attempting to define a favicon. This way, there
// is only one favicon element on the page at a time.
this.removeExternalLinkElements();
}
// ---
// PUBLIC METHODS.
// ---
// I activate the favicon with the given name / identifier.
public activate( name: string ) : void {
if ( ! this.icons[ name ] ) {
throw( new Error( `Favicon for [ ${ name } ] not found.` ) );
}
this.setNode( this.icons[ name ].type, this.icons[ name ].href );
}
// I activate the default favicon (with isDefault set to True).
public reset() : void {
for ( var name of Object.keys( this.icons ) ) {
var icon = this.icons[ name ];
if ( icon.isDefault ) {
this.setNode( icon.type, icon.href );
return;
}
}
// If we made it this far, none of the favicons were flagged as default. In that
// case, let's just remove the favicon node altogether.
this.removeNode();
}
// ---
// PRIVATE METHODS.
// ---
// I inject the favicon element into the document header.
private addNode( type: string, href: string ) : void {
var linkElement = document.createElement( "link" );
linkElement.setAttribute( "id", this.elementId );
linkElement.setAttribute( "rel", "icon" );
linkElement.setAttribute( "type", type );
linkElement.setAttribute( "href", href );
document.head.appendChild( linkElement );
}
// I return an augmented HREF value with a cache-busting query-string parameter.
private cacheBustHref( href: string ) : string {
var augmentedHref = ( href.indexOf( "?" ) === -1 )
? `${ href }?faviconCacheBust=${ Date.now() }`
: `${ href }&faviconCacheBust=${ Date.now() }`
;
return( augmentedHref );
}
// I remove any favicon nodes that are not controlled by this service.
private removeExternalLinkElements() : void {
var linkElements = document.querySelectorAll( "link[ rel ~= 'icon' i]" );
for ( var linkElement of Array.from( linkElements ) ) {
linkElement.parentNode.removeChild( linkElement );
}
}
// I remove the favicon node from the document header.
private removeNode() : void {
var linkElement = document.head.querySelector( "#" + this.elementId );
if ( linkElement ) {
document.head.removeChild( linkElement );
}
}
// I remove the existing favicon node and inject a new favicon node with the given
// element settings.
private setNode( type: string, href: string ) : void {
var augmentedHref = this.useCacheBusting
? this.cacheBustHref( href )
: href
;
this.removeNode();
this.addNode( type, augmentedHref );
}
}