Files
remark42/frontend/app/embed.ts
T
2021-08-08 14:25:10 -05:00

145 lines
3.9 KiB
TypeScript

import { NODE_ID, COMMENT_NODE_CLASSNAME_PREFIX } from 'common/constants.config';
import { parseMessage, postMessageToIframe } from 'utils/post-message';
import { createIframe } from 'utils/create-iframe';
import type { Theme } from 'common/types';
import { closeProfile, openProfile } from 'profile';
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
function init() {
window.REMARK42 = window.REMARK42 || {};
window.REMARK42.createInstance = createInstance;
createInstance(window.remark_config);
window.dispatchEvent(new Event('REMARK42::ready'));
}
function createInstance(config: typeof window.remark_config) {
const root = document.getElementById(NODE_ID);
if (!root) {
throw new Error("Remark42: Can't find root node.");
}
if (!window.remark_config) {
throw new Error('Remark42: Config object is undefined.');
}
if (!window.remark_config.site_id) {
throw new Error('Remark42: Site ID is undefined.');
}
let titleObserver: MutationObserver | null = null;
config.url = (config.url || `${window.location.origin}${window.location.pathname}`).split('#')[0];
const iframe = (root.firstElementChild as HTMLIFrameElement) || createIframe(config);
root.appendChild(iframe);
window.addEventListener('message', handleReceiveMessage);
window.addEventListener('hashchange', handleHashChange);
document.addEventListener('click', postClickOutsideToIframe);
const titleElement = document.querySelector('title');
if (titleElement) {
titleObserver = new MutationObserver((mutations) => postTitleToIframe(mutations[0].target.textContent!));
titleObserver.observe(titleElement, {
subtree: true,
characterData: true,
childList: true,
});
}
function handleReceiveMessage(event: MessageEvent): void {
const data = parseMessage(event);
if (typeof data.height === 'number') {
iframe.style.height = `${data.height}px`;
}
if (typeof data.scrollTo === 'number') {
window.scrollTo(window.pageXOffset, data.scrollTo + iframe.getBoundingClientRect().top + window.pageYOffset);
}
if (typeof data.profile === 'object') {
if (data.profile === null) {
closeProfile();
} else {
openProfile({ ...config, ...data.profile });
}
}
if (data.signout === true) {
postMessageToIframe(iframe, { signout: true });
}
if (data.inited === true) {
postHashToIframe();
postTitleToIframe(document.title);
}
}
function postHashToIframe(hash = window.location.hash) {
if (!hash.startsWith(`#${COMMENT_NODE_CLASSNAME_PREFIX}`)) {
return;
}
postMessageToIframe(iframe, { hash });
}
function handleHashChange(evt: HashChangeEvent) {
const url = new URL(evt.newURL);
postHashToIframe(url.hash);
}
function postTitleToIframe(title: string) {
postMessageToIframe(iframe, { title });
}
function postClickOutsideToIframe(evt: MouseEvent) {
if (iframe.contains(evt.target as Node)) {
return;
}
postMessageToIframe(iframe, { clickOutside: true });
}
function changeTheme(theme: Theme) {
window.remark_config.theme = theme;
postMessageToIframe(iframe, { theme });
}
function destroy() {
window.removeEventListener('message', handleReceiveMessage);
window.removeEventListener('hashchange', handleHashChange);
document.removeEventListener('click', postClickOutsideToIframe);
if (titleObserver) {
titleObserver.disconnect();
// Allow browser to drop observer and iframe captured in callback
// to prevent attempts to send messages to detached frame
titleObserver = null;
}
iframe.remove();
}
// TODO: These do not appear in Chrome DevTools
window.REMARK42.changeTheme = changeTheme;
window.REMARK42.destroy = () => {
destroy();
delete window.REMARK42.changeTheme;
delete window.REMARK42.destroy;
};
return {
changeTheme,
destroy,
};
}