Skip to content
This repository was archived by the owner on Aug 21, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions packages/client-core/src/common/components/MetaTags/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React, { Component, createContext, ReactNode, useEffect, useState } from 'react'
// import ReactDOM from 'react-dom'
import { createRoot, Root } from 'react-dom/client'

import {
appendChild,
getDuplicateCanonical,
getDuplicateElementById,
getDuplicateMeta,
getDuplicateTitle,
removeChild
} from './utils'

/**
* This component is taken from https://github.com/s-yadav/react-meta-tags
* Here we fixed breaking changes introduced due to react 18
*/

type MetaContextType = {
extract?: (children: ReactNode | undefined) => void
}

const MetaContext = createContext<MetaContextType>({})

type AppWithCallbackProps = {
children: ReactNode
element: HTMLDivElement
lastChildStr: string
onLastChildStr: (lastChildStr: string) => void
}

const AppWithCallback = ({ children, element, lastChildStr, onLastChildStr }: AppWithCallbackProps) => {
useEffect(() => {
const childStr = element.innerHTML

//if html is not changed return
if (lastChildStr === childStr) {
return
}

onLastChildStr(childStr)

const tempHead = element.querySelector('.react-head-temp')

// .react-head-temp might not exist when triggered from async action
if (tempHead === null) {
return
}

let childNodes = Array.prototype.slice.call(tempHead.children)

const head = document.head
const headHtml = head.innerHTML

//filter children remove if children has not been changed
childNodes = childNodes.filter((child) => {
return headHtml.indexOf(child.outerHTML) === -1
})

//create clone of childNodes
childNodes = childNodes.map((child) => child.cloneNode(true))

//remove duplicate title and meta from head
childNodes.forEach((child) => {
const tag = child.tagName.toLowerCase()
if (tag === 'title') {
const title = getDuplicateTitle()
if (title) removeChild(head, title)
} else if (child.id) {
// if the element has id defined remove the existing element with that id
const elm = getDuplicateElementById(child)
if (elm) removeChild(head, elm)
} else if (tag === 'meta') {
const meta = getDuplicateMeta(child)
if (meta) removeChild(head, meta)
} else if (tag === 'link' && child.rel === 'canonical') {
const link = getDuplicateCanonical()
if (link) removeChild(head, link)
}
})

appendChild(document.head, childNodes)
})

return <>{children}</>
}

type Props = {
children?: ReactNode | undefined
}

/** An wrapper component to wrap element which need to shifted to head **/
class MetaTags extends Component<Props> {
static contextType = MetaContext
temporaryElement: HTMLDivElement
temporaryRoot: Root
lastChildStr: string

componentDidMount() {
this.temporaryElement = document.createElement('div')
this.temporaryRoot = createRoot(this.temporaryElement)
this.handleChildrens()
}
componentDidUpdate(oldProps) {
if (oldProps.children !== this.props.children) {
this.handleChildrens()
}
}
componentWillUnmount() {
if (this.temporaryRoot) {
this.temporaryRoot.unmount()
}
}
extractChildren() {
const { extract } = this.context as MetaContextType
const { children } = this.props

if (!children) {
return
}

if (extract) {
extract(children)
}
}
handleChildrens() {
const { children } = this.props
if ((this.context as MetaContextType).extract || !children) {
return
}

const headComponent = <div className="react-head-temp">{children}</div>

this.temporaryRoot.render(
<AppWithCallback
element={this.temporaryElement}
lastChildStr={this.lastChildStr}
onLastChildStr={(lastChildStr) => (this.lastChildStr = lastChildStr)}
>
{headComponent}
</AppWithCallback>
)
}
render() {
this.extractChildren()
return null
}
}

export default MetaTags
57 changes: 57 additions & 0 deletions packages/client-core/src/common/components/MetaTags/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const camelCaseProps = ['itemProp']
const uniqueIdentifiersI = ['property', 'name', 'itemprop']

/**
* This code is taken from https://github.com/s-yadav/react-meta-tags
* Here we fixed breaking changes introduced due to react 18
**/

function filterOutMetaWithId(metas) {
metas = Array.prototype.slice.call(metas || [])
return metas.filter((meta) => !meta.id)
}

export function getDuplicateTitle() {
return document.head.querySelectorAll('title')
}

export function getDuplicateCanonical() {
return document.head.querySelectorAll('link[rel="canonical"]')
}

export function getDuplicateElementById({ id }) {
return id && document.head.querySelector(`#${id}`)
}

export function getDuplicateMeta(meta) {
const head = document.head

//for any other unique identifier check if metas already available with same identifier which doesn't have id
return uniqueIdentifiersI.reduce((duplicates, identifier) => {
const identifierValue = meta.getAttribute(identifier)
return identifierValue
? duplicates.concat(filterOutMetaWithId(head.querySelectorAll(`[${identifier} = "${identifierValue}"]`)))
: duplicates
}, [])
}

//function to append childrens on a parent
export function appendChild(parent, childrens) {
if (childrens.length === undefined) childrens = [childrens]

const docFrag = document.createDocumentFragment()

//we used for loop instead of forEach because childrens can be array like object
for (let i = 0, ln = childrens.length; i < ln; i++) {
docFrag.appendChild(childrens[i])
}

parent.appendChild(docFrag)
}

export function removeChild(parent, childrens) {
if (childrens.length === undefined) childrens = [childrens]
for (let i = 0, ln = childrens.length; i < ln; i++) {
parent.removeChild(childrens[i])
}
}
1 change: 0 additions & 1 deletion packages/client/defaultDeps.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@
"react-i18next",
"react-json-tree",
"react-material-ui-form-validator",
"react-meta-tags",
"react-router",
"react-router-dom",
"react-select",
Expand Down
1 change: 0 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
"react-full-screen": "1.1.1",
"react-i18next": "11.16.6",
"react-json-tree": "0.15.2",
"react-meta-tags": "^1.0.1",
"react-responsive": "9.0.0-beta.6",
"react-router-dom": "5.3.0",
"sass": "1.50.0",
Expand Down
3 changes: 1 addition & 2 deletions packages/client/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
useClientSettingState
} from '@xrengine/client-core/src/admin/services/Setting/ClientSettingService'
import { initGA, logPageView } from '@xrengine/client-core/src/common/components/analytics'
import MetaTags from '@xrengine/client-core/src/common/components/MetaTags'
import { defaultAction } from '@xrengine/client-core/src/common/components/NotificationActions'
import { ProjectService, useProjectState } from '@xrengine/client-core/src/common/services/ProjectService'
import InviteToast from '@xrengine/client-core/src/components/InviteToast'
Expand All @@ -24,8 +25,6 @@ import RouterComp from '../route/public'

import './styles.scss'

import MetaTags from 'react-meta-tags'

import {
AdminCoilSettingService,
useCoilSettingState
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { Fragment, useEffect, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import MetaTags from 'react-meta-tags'
import { Redirect } from 'react-router-dom'

import { useClientSettingState } from '@xrengine/client-core/src/admin/services/Setting/ClientSettingService'
import styles from '@xrengine/client-core/src/admin/styles/admin.module.scss'
import MetaTags from '@xrengine/client-core/src/common/components/MetaTags'
import { NotificationService } from '@xrengine/client-core/src/common/services/NotificationService'
import ProfileMenu from '@xrengine/client-core/src/user/components/UserMenu/menus/ProfileMenu'
import SettingMenu from '@xrengine/client-core/src/user/components/UserMenu/menus/SettingMenu'
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/pages/room.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import MetaTags from 'react-meta-tags'

import { useClientSettingState } from '@xrengine/client-core/src/admin/services/Setting/ClientSettingService'
import MetaTags from '@xrengine/client-core/src/common/components/MetaTags'
import RoomMenu from '@xrengine/client-core/src/user/components/UserMenu/menus/RoomMenu'

export const RoomPage = (): any => {
Expand Down