Viewing File: /home/markqprx/iniasli.pro/client/seo/helmet.tsx
import {Children, memo, ReactElement} from 'react';
import {shallowEqual} from '../utils/shallow-equal';
import {MetaTag} from './meta-tag';
import {TitleMetaTagChildren} from './static-page-title';
import {useTrans, UseTransReturn} from '../i18n/use-trans';
import {isSsr} from '@common/utils/dom/is-ssr';
const rafPolyfill = (() => {
let clock = Date.now();
return (callback: Function) => {
const currentTime = Date.now();
if (currentTime - clock > 16) {
clock = currentTime;
callback(currentTime);
} else {
setTimeout(() => {
rafPolyfill(callback);
}, 0);
}
};
})();
const cafPolyfill = (id: string | number) => clearTimeout(id);
const requestAnimationFrame = !isSsr()
? window.requestAnimationFrame
: global.requestAnimationFrame || rafPolyfill;
const cancelAnimationFrame = !isSsr()
? window.cancelAnimationFrame
: global.cancelAnimationFrame || cafPolyfill;
export const helmetAttribute = 'data-be-helmet';
let rafId: number | null;
interface HelmetProps {
children?: ReactElement | ReactElement[];
tags?: MetaTag[];
}
export const Helmet = memo(({children, tags}: HelmetProps) => {
const {trans} = useTrans();
if (isSsr()) return null;
if (!tags && children) {
tags = mapChildrenToTags(children, trans);
}
updateTags(tags);
return null;
}, shallowEqual);
function mapChildrenToTags(
children: ReactElement | ReactElement[],
trans: UseTransReturn['trans'],
): MetaTag[] {
return Children.map(children, child => {
switch (child.type) {
case 'title':
return {
nodeName: 'title',
_text: titleTagChildrenToString(child.props.children, trans),
};
case 'meta':
return {...child.props, nodeName: 'meta'};
}
});
}
function titleTagChildrenToString(
children: TitleMetaTagChildren,
trans: UseTransReturn['trans'],
): string {
if (children == null) return '';
if (typeof children === 'string') return children;
if (Array.isArray(children)) {
return children.map(c => titleTagChildrenToString(c, trans)).join('');
}
if ('message' in children) {
return trans(children);
}
return trans(children.props);
}
function removeOldTags() {
if (isSsr()) return;
document.head
.querySelectorAll(
'meta:not([data-keep]), script meta:not([data-keep]), title, link[rel="canonical"]',
)
.forEach(tag => {
document.head.removeChild(tag);
});
}
function updateTags(tags?: MetaTag[] | string) {
if (rafId) {
cancelAnimationFrame(rafId);
}
rafId = requestAnimationFrame(() => {
removeOldTags();
if (typeof tags === 'string') {
const template = document.createElement('template');
template.innerHTML = tags;
template.content.childNodes.forEach(node => {
if (node instanceof HTMLElement) {
node.setAttribute(helmetAttribute, 'true');
document.head.prepend(node);
}
});
} else {
tags?.forEach(tag => {
updateTag(tag);
});
}
rafId = null;
});
}
function updateTag(tag: MetaTag) {
// update title
if (tag.nodeName === 'title') {
if (typeof tag._text !== 'undefined' && document.title !== tag._text) {
document.title = tag._text;
}
return;
}
// update <meta> tag
const newElement = document.createElement(tag.nodeName);
for (const key in tag) {
const attribute = key as keyof MetaTag;
if (attribute === 'nodeName') continue;
if (attribute === '_text') {
newElement.textContent =
typeof tag._text === 'string' ? tag._text : JSON.stringify(tag._text);
} else {
const value = tag[attribute] == null ? '' : tag[attribute];
newElement.setAttribute(attribute, value as string);
}
}
newElement.setAttribute(helmetAttribute, 'true');
document.head.prepend(newElement);
}
Back to Directory
File Manager