1
- import { booleanHtmlAttributes } from '../../shared/constants'
2
- import { toArray , includes } from '../../utils/array'
1
+ import { booleanHtmlAttributes , commonDataAttributes } from '../../shared/constants'
2
+ import { includes } from '../../utils/array'
3
+ import { queryElements , getElementsKey } from '../../utils/elements.js'
3
4
4
5
/**
5
6
* Updates meta tags inside <head> and <body> on the client. Borrowed from `react-helmet`:
@@ -9,11 +10,16 @@ import { toArray, includes } from '../../utils/array'
9
10
* @param {(Array<Object>|Object) } tags - an array of tag objects or a single object in case of base
10
11
* @return {Object } - a representation of what tags changed
11
12
*/
12
- export default function updateTag ( appId , { attribute, tagIDKeyName } = { } , type , tags , headTag , bodyTag ) {
13
- const oldHeadTags = toArray ( headTag . querySelectorAll ( `${ type } [${ attribute } ="${ appId } "], ${ type } [data-${ tagIDKeyName } ]` ) )
14
- const oldBodyTags = toArray ( bodyTag . querySelectorAll ( `${ type } [${ attribute } ="${ appId } "][data-body="true"], ${ type } [data-${ tagIDKeyName } ][data-body="true"]` ) )
15
- const dataAttributes = [ tagIDKeyName , 'body' ]
16
- const newTags = [ ]
13
+ export default function updateTag ( appId , { attribute, tagIDKeyName } = { } , type , tags , head , body ) {
14
+ const dataAttributes = [ tagIDKeyName , ...commonDataAttributes ]
15
+ const newElements = [ ]
16
+
17
+ const queryOptions = { appId, attribute, type, tagIDKeyName }
18
+ const currentElements = {
19
+ head : queryElements ( head , queryOptions ) ,
20
+ pbody : queryElements ( body , queryOptions , { pbody : true } ) ,
21
+ body : queryElements ( body , queryOptions , { body : true } )
22
+ }
17
23
18
24
if ( tags . length > 1 ) {
19
25
// remove duplicates that could have been found by merging tags
@@ -29,64 +35,88 @@ export default function updateTag (appId, { attribute, tagIDKeyName } = {}, type
29
35
}
30
36
31
37
if ( tags . length ) {
32
- tags . forEach ( ( tag ) => {
38
+ for ( const tag of tags ) {
33
39
const newElement = document . createElement ( type )
34
-
35
40
newElement . setAttribute ( attribute , appId )
36
41
37
- const oldTags = tag . body !== true ? oldHeadTags : oldBodyTags
38
-
39
42
for ( const attr in tag ) {
40
43
if ( tag . hasOwnProperty ( attr ) ) {
41
44
if ( attr === 'innerHTML' ) {
42
45
newElement . innerHTML = tag . innerHTML
43
- } else if ( attr === 'cssText' ) {
46
+ continue
47
+ }
48
+
49
+ if ( attr === 'cssText' ) {
44
50
if ( newElement . styleSheet ) {
45
51
/* istanbul ignore next */
46
52
newElement . styleSheet . cssText = tag . cssText
47
53
} else {
48
54
newElement . appendChild ( document . createTextNode ( tag . cssText ) )
49
55
}
50
- } else {
51
- const _attr = includes ( dataAttributes , attr )
52
- ? `data-${ attr } `
53
- : attr
54
-
55
- const isBooleanAttribute = includes ( booleanHtmlAttributes , attr )
56
- if ( isBooleanAttribute && ! tag [ attr ] ) {
57
- continue
58
- }
56
+ continue
57
+ }
58
+
59
+ const _attr = includes ( dataAttributes , attr )
60
+ ? `data-${ attr } `
61
+ : attr
59
62
60
- const value = isBooleanAttribute ? '' : tag [ attr ]
61
- newElement . setAttribute ( _attr , value )
63
+ const isBooleanAttribute = includes ( booleanHtmlAttributes , attr )
64
+ if ( isBooleanAttribute && ! tag [ attr ] ) {
65
+ continue
62
66
}
67
+
68
+ const value = isBooleanAttribute ? '' : tag [ attr ]
69
+ newElement . setAttribute ( _attr , value )
63
70
}
64
71
}
65
72
73
+ const oldElements = currentElements [ getElementsKey ( tag ) ]
74
+
66
75
// Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
67
76
let indexToDelete
68
- const hasEqualElement = oldTags . some ( ( existingTag , index ) => {
77
+ const hasEqualElement = oldElements . some ( ( existingTag , index ) => {
69
78
indexToDelete = index
70
79
return newElement . isEqualNode ( existingTag )
71
80
} )
72
81
73
82
if ( hasEqualElement && ( indexToDelete || indexToDelete === 0 ) ) {
74
- oldTags . splice ( indexToDelete , 1 )
83
+ oldElements . splice ( indexToDelete , 1 )
75
84
} else {
76
- newTags . push ( newElement )
85
+ newElements . push ( newElement )
77
86
}
78
- } )
87
+ }
88
+ }
89
+
90
+ let oldElements = [ ]
91
+ for ( const current of Object . values ( currentElements ) ) {
92
+ oldElements = [
93
+ ...oldElements ,
94
+ ...current
95
+ ]
96
+ }
97
+
98
+ // remove old elements
99
+ for ( const element of oldElements ) {
100
+ element . parentNode . removeChild ( element )
79
101
}
80
102
81
- const oldTags = oldHeadTags . concat ( oldBodyTags )
82
- oldTags . forEach ( tag => tag . parentNode . removeChild ( tag ) )
83
- newTags . forEach ( ( tag ) => {
84
- if ( tag . getAttribute ( 'data-body' ) === 'true' ) {
85
- bodyTag . appendChild ( tag )
86
- } else {
87
- headTag . appendChild ( tag )
103
+ // insert new elements
104
+ for ( const element of newElements ) {
105
+ if ( element . hasAttribute ( 'data-body' ) ) {
106
+ body . appendChild ( element )
107
+ continue
88
108
}
89
- } )
90
109
91
- return { oldTags, newTags }
110
+ if ( element . hasAttribute ( 'data-pbody' ) ) {
111
+ body . insertBefore ( element , body . firstChild )
112
+ continue
113
+ }
114
+
115
+ head . appendChild ( element )
116
+ }
117
+
118
+ return {
119
+ oldTags : oldElements ,
120
+ newTags : newElements
121
+ }
92
122
}
0 commit comments