forked from facebook/docusaurus
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhtmlTags.ts
More file actions
119 lines (113 loc) · 3.21 KB
/
htmlTags.ts
File metadata and controls
119 lines (113 loc) · 3.21 KB
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
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import htmlTags from 'html-tags';
import voidHtmlTags from 'html-tags/void';
import escapeHTML from 'escape-html';
import type {
Props,
HtmlTagObject,
HtmlTags,
LoadedPlugin,
RouterType,
} from '@docusaurus/types';
function assertIsHtmlTagObject(val: unknown): asserts val is HtmlTagObject {
if (typeof val !== 'object' || !val) {
throw new Error(`"${val}" is not a valid HTML tag object.`);
}
if (typeof (val as HtmlTagObject).tagName !== 'string') {
throw new Error(
`${JSON.stringify(
val,
)} is not a valid HTML tag object. "tagName" must be defined as a string.`,
);
}
if (!(htmlTags as string[]).includes((val as HtmlTagObject).tagName)) {
throw new Error(
`Error loading ${JSON.stringify(val)}, "${
(val as HtmlTagObject).tagName
}" is not a valid HTML tag.`,
);
}
}
function hashRouterAbsoluteToRelativeTagAttribute(
name: string,
value: string,
): string {
if ((name === 'src' || name === 'href') && value.startsWith('/')) {
return `.${value}`;
}
return value;
}
function htmlTagObjectToString({
tag,
router,
}: {
tag: unknown;
router: RouterType;
}): string {
assertIsHtmlTagObject(tag);
const isVoidTag = (voidHtmlTags as string[]).includes(tag.tagName);
const tagAttributes = tag.attributes ?? {};
const attributes = Object.keys(tagAttributes)
.map((attr) => {
let value = tagAttributes[attr]!;
if (typeof value === 'boolean') {
return value ? attr : undefined;
}
if (router === 'hash') {
value = hashRouterAbsoluteToRelativeTagAttribute(attr, value);
}
return `${attr}="${escapeHTML(value)}"`;
})
.filter((str): str is string => Boolean(str));
const openingTag = `<${[tag.tagName].concat(attributes).join(' ')}>`;
const innerHTML = (!isVoidTag && tag.innerHTML) || '';
const closingTag = isVoidTag ? '' : `</${tag.tagName}>`;
return openingTag + innerHTML + closingTag;
}
function createHtmlTagsString({
tags,
router,
}: {
tags: HtmlTags | undefined;
router: RouterType;
}): string {
return (Array.isArray(tags) ? tags : [tags])
.filter(Boolean)
.map((val) =>
typeof val === 'string' ? val : htmlTagObjectToString({tag: val, router}),
)
.join('\n');
}
/**
* Runs the `injectHtmlTags` lifecycle, and aggregates all plugins' tags into
* directly render-able HTML markup.
*/
export function loadHtmlTags({
plugins,
router,
}: {
plugins: LoadedPlugin[];
router: RouterType;
}): Pick<Props, 'headTags' | 'preBodyTags' | 'postBodyTags'> {
const pluginHtmlTags = plugins.map(
(plugin) => plugin.injectHtmlTags?.({content: plugin.content}) ?? {},
);
const tagTypes = ['headTags', 'preBodyTags', 'postBodyTags'] as const;
return Object.fromEntries(
_.zip(
tagTypes,
tagTypes.map((type) =>
pluginHtmlTags
.map((tags) => createHtmlTagsString({tags: tags[type], router}))
.join('\n')
.trim(),
),
),
) as Pick<Props, 'headTags' | 'preBodyTags' | 'postBodyTags'>;
}