Fixes #14638 Uses Domino - https://github.com/fgnass/domino and removes dependency on Parse5. The DOCUMENT and nativeElement were never typed earlier and were different on the browser(DOM nodes) and the server(Parse5 nodes). With this change, platform-server also exposes a DOCUMENT and nativeElement that is closer to the client. If you were relying on nativeElement on the server, you would have to change your code to use the DOM API now instead of Parse5 AST API. Removes the need to add services for each and every Document manipulation like Title/Meta etc. This does *not* provide a global variable 'document' or 'window' on the server. You still have to inject DOCUMENT to get the document backing the current platform server instance.
117 lines
3.7 KiB
TypeScript
117 lines
3.7 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {Inject, Injectable} from '@angular/core';
|
|
|
|
import {DomAdapter, getDOM} from '../dom/dom_adapter';
|
|
import {DOCUMENT} from '../dom/dom_tokens';
|
|
|
|
|
|
/**
|
|
* Represents a meta element.
|
|
*
|
|
* @experimental
|
|
*/
|
|
export type MetaDefinition = {
|
|
charset?: string; content?: string; httpEquiv?: string; id?: string; itemprop?: string;
|
|
name?: string;
|
|
property?: string;
|
|
scheme?: string;
|
|
url?: string;
|
|
} &
|
|
{
|
|
// TODO(IgorMinar): this type looks wrong
|
|
[prop: string]: string;
|
|
};
|
|
|
|
/**
|
|
* A service that can be used to get and add meta tags.
|
|
*
|
|
* @experimental
|
|
*/
|
|
@Injectable()
|
|
export class Meta {
|
|
private _dom: DomAdapter;
|
|
constructor(@Inject(DOCUMENT) private _doc: any) { this._dom = getDOM(); }
|
|
|
|
addTag(tag: MetaDefinition, forceCreation: boolean = false): HTMLMetaElement|null {
|
|
if (!tag) return null;
|
|
return this._getOrCreateElement(tag, forceCreation);
|
|
}
|
|
|
|
addTags(tags: MetaDefinition[], forceCreation: boolean = false): HTMLMetaElement[] {
|
|
if (!tags) return [];
|
|
return tags.reduce((result: HTMLMetaElement[], tag: MetaDefinition) => {
|
|
if (tag) {
|
|
result.push(this._getOrCreateElement(tag, forceCreation));
|
|
}
|
|
return result;
|
|
}, []);
|
|
}
|
|
|
|
getTag(attrSelector: string): HTMLMetaElement|null {
|
|
if (!attrSelector) return null;
|
|
return this._dom.querySelector(this._doc, `meta[${attrSelector}]`) || null;
|
|
}
|
|
|
|
getTags(attrSelector: string): HTMLMetaElement[] {
|
|
if (!attrSelector) return [];
|
|
const list /*NodeList*/ = this._dom.querySelectorAll(this._doc, `meta[${attrSelector}]`);
|
|
return list ? [].slice.call(list) : [];
|
|
}
|
|
|
|
updateTag(tag: MetaDefinition, selector?: string): HTMLMetaElement|null {
|
|
if (!tag) return null;
|
|
selector = selector || this._parseSelector(tag);
|
|
const meta: HTMLMetaElement = this.getTag(selector) !;
|
|
if (meta) {
|
|
return this._setMetaElementAttributes(tag, meta);
|
|
}
|
|
return this._getOrCreateElement(tag, true);
|
|
}
|
|
|
|
removeTag(attrSelector: string): void { this.removeTagElement(this.getTag(attrSelector) !); }
|
|
|
|
removeTagElement(meta: HTMLMetaElement): void {
|
|
if (meta) {
|
|
this._dom.remove(meta);
|
|
}
|
|
}
|
|
|
|
private _getOrCreateElement(meta: MetaDefinition, forceCreation: boolean = false):
|
|
HTMLMetaElement {
|
|
if (!forceCreation) {
|
|
const selector: string = this._parseSelector(meta);
|
|
const elem: HTMLMetaElement = this.getTag(selector) !;
|
|
// It's allowed to have multiple elements with the same name so it's not enough to
|
|
// just check that element with the same name already present on the page. We also need to
|
|
// check if element has tag attributes
|
|
if (elem && this._containsAttributes(meta, elem)) return elem;
|
|
}
|
|
const element: HTMLMetaElement = this._dom.createElement('meta') as HTMLMetaElement;
|
|
this._setMetaElementAttributes(meta, element);
|
|
const head = this._dom.getElementsByTagName(this._doc, 'head')[0];
|
|
this._dom.appendChild(head, element);
|
|
return element;
|
|
}
|
|
|
|
private _setMetaElementAttributes(tag: MetaDefinition, el: HTMLMetaElement): HTMLMetaElement {
|
|
Object.keys(tag).forEach((prop: string) => this._dom.setAttribute(el, prop, tag[prop]));
|
|
return el;
|
|
}
|
|
|
|
private _parseSelector(tag: MetaDefinition): string {
|
|
const attr: string = tag.name ? 'name' : 'property';
|
|
return `${attr}="${tag[attr]}"`;
|
|
}
|
|
|
|
private _containsAttributes(tag: MetaDefinition, elem: HTMLMetaElement): boolean {
|
|
return Object.keys(tag).every((key: string) => this._dom.getAttribute(elem, key) === tag[key]);
|
|
}
|
|
}
|