post messages from iframe
- fix user info opening - remove iframe height checks by interval - update height by mutation observer events - rename events
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
import { parseQuery } from 'utils/parseQuery';
|
||||
|
||||
import type { UserInfo } from './types';
|
||||
|
||||
export const userInfo: UserInfo = parseQuery();
|
||||
|
||||
export const id = userInfo.id;
|
||||
export const name = userInfo.name;
|
||||
export const picture = userInfo.picture;
|
||||
@@ -6,7 +6,7 @@ import b from 'bem-react-helper';
|
||||
import { User, Sorting, Theme, PostInfo } from 'common/types';
|
||||
import { IS_STORAGE_AVAILABLE, IS_THIRD_PARTY } from 'common/constants';
|
||||
import { requestDeletion } from 'utils/email';
|
||||
import { postMessage } from 'utils/postMessage';
|
||||
import { postMessageToParent } from 'utils/postMessage';
|
||||
import { getHandleClickProps } from 'common/accessibility';
|
||||
import { StoreState } from 'store';
|
||||
import { Dropdown, DropdownItem } from 'components/dropdown';
|
||||
@@ -75,11 +75,11 @@ class AuthPanelComponent extends Component<Props, State> {
|
||||
toggleUserInfoVisibility = () => {
|
||||
const { user } = this.props;
|
||||
|
||||
if (user === null) {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
postMessage({ isUserInfoShown: true, user });
|
||||
postMessageToParent({ profile: user });
|
||||
};
|
||||
|
||||
renderAuthorized = (user: User) => {
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import { parseMessage, postMessageToParent } from 'utils/postMessage';
|
||||
|
||||
function handleChangeIframeSize(element: HTMLElement) {
|
||||
const { top } = element.getBoundingClientRect();
|
||||
const height = window.scrollY + Math.abs(top) + element.scrollHeight + 20;
|
||||
const height = Math.max(window.scrollY + Math.abs(top) + element.scrollHeight + 20, document.body.offsetHeight);
|
||||
|
||||
if (window.innerHeight > height) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.body.style.setProperty('min-height', `${height}px`);
|
||||
postMessageToParent({ height });
|
||||
}
|
||||
|
||||
export function useDropdown(disableClosing?: boolean) {
|
||||
@@ -19,28 +16,20 @@ export function useDropdown(disableClosing?: boolean) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!showDropdown) {
|
||||
const dropdownElement = rootRef.current;
|
||||
|
||||
if (!showDropdown || !dropdownElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dropdownElement = rootRef.current;
|
||||
|
||||
handleChangeIframeSize(dropdownElement);
|
||||
|
||||
function handleMessageFromParent(evt: MessageEvent) {
|
||||
if (typeof evt.data !== 'string' || disableClosing) {
|
||||
const data = parseMessage(evt);
|
||||
|
||||
if (disableClosing && data.clickOutside) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(evt.data);
|
||||
|
||||
if (!data.clickOutside) {
|
||||
return;
|
||||
}
|
||||
|
||||
setShowDropdown(false);
|
||||
} catch (e) {}
|
||||
setShowDropdown(false);
|
||||
}
|
||||
|
||||
function handleClickOutside(evt: MouseEvent) {
|
||||
@@ -64,9 +53,13 @@ export function useDropdown(disableClosing?: boolean) {
|
||||
const dropdownElement = rootRef.current;
|
||||
|
||||
if (!dropdownElement || !showDropdown) {
|
||||
handleChangeIframeSize(document.body);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
handleChangeIframeSize(dropdownElement);
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
handleChangeIframeSize(dropdownElement);
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Avatar } from 'components/avatar';
|
||||
import { Button } from 'components/button';
|
||||
import { Countdown } from 'components/countdown';
|
||||
import { getPreview, uploadImage } from 'common/api';
|
||||
import { postMessage } from 'utils/postMessage';
|
||||
import { postMessageToParent } from 'utils/postMessage';
|
||||
import { FormattedMessage, IntlShape, defineMessages } from 'react-intl';
|
||||
import { getVoteMessage, VoteMessagesTypes } from './getVoteMessage';
|
||||
import { getBlockingDurations } from './getBlockingDurations';
|
||||
@@ -211,14 +211,7 @@ export class Comment extends Component<CommentProps, State> {
|
||||
};
|
||||
|
||||
toggleUserInfoVisibility = () => {
|
||||
if (!window.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { user } = this.props.data;
|
||||
const data = JSON.stringify({ isUserInfoShown: true, user });
|
||||
|
||||
window.parent.postMessage(data, '*');
|
||||
postMessageToParent({ profile: this.props.data.user });
|
||||
};
|
||||
|
||||
togglePin = () => {
|
||||
@@ -357,21 +350,23 @@ export class Comment extends Component<CommentProps, State> {
|
||||
this.props.setReplyEditState!({ id: this.props.data.id, state: CommentMode.None });
|
||||
};
|
||||
|
||||
scrollToParent = (e: Event) => {
|
||||
const {
|
||||
data: { pid },
|
||||
} = this.props;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
scrollToParent = (evt: Event) => {
|
||||
const { pid } = this.props.data;
|
||||
const parentCommentNode = document.getElementById(`${COMMENT_NODE_CLASSNAME_PREFIX}${pid}`);
|
||||
|
||||
if (parentCommentNode) {
|
||||
const top = parentCommentNode.getBoundingClientRect().top;
|
||||
if (!postMessage({ scrollTo: top })) {
|
||||
parentCommentNode.scrollIntoView();
|
||||
}
|
||||
evt.preventDefault();
|
||||
|
||||
if (!parentCommentNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const top = parentCommentNode.getBoundingClientRect().top;
|
||||
|
||||
if (postMessageToParent({ scrollTo: top })) {
|
||||
return;
|
||||
}
|
||||
|
||||
parentCommentNode.scrollIntoView();
|
||||
};
|
||||
|
||||
copyComment = () => {
|
||||
|
||||
@@ -134,25 +134,23 @@ export class Dropdown extends Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
receiveMessage(event: MessageEvent<{ clickOutside?: boolean }>) {
|
||||
try {
|
||||
const data = parseMessage(event);
|
||||
receiveMessage(evt: MessageEvent) {
|
||||
const data = parseMessage(evt);
|
||||
|
||||
if (!data || !data.clickOutside || !this.state.isActive) {
|
||||
return;
|
||||
if (!data.clickOutside || !this.state.isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
contentTranslateX: 0,
|
||||
isActive: false,
|
||||
},
|
||||
() => {
|
||||
this.__onClose();
|
||||
this.props.onClose?.(this.rootNode.current!);
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
contentTranslateX: 0,
|
||||
isActive: false,
|
||||
},
|
||||
() => {
|
||||
this.__onClose();
|
||||
this.props.onClose && this.props.onClose(this.rootNode.current!);
|
||||
}
|
||||
);
|
||||
} catch (e) {}
|
||||
);
|
||||
}
|
||||
|
||||
onOutsideClick(e: MouseEvent) {
|
||||
@@ -171,13 +169,11 @@ export class Dropdown extends Component<Props, State> {
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('click', this.onOutsideClick);
|
||||
|
||||
window.addEventListener('message', this.receiveMessage);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.onOutsideClick);
|
||||
|
||||
window.removeEventListener('message', this.receiveMessage);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import b from 'bem-react-helper';
|
||||
import { IntlShape, useIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import type { Sorting, Theme } from 'common/types';
|
||||
import type { Sorting } from 'common/types';
|
||||
import type { StoreState } from 'store';
|
||||
import {
|
||||
COMMENT_NODE_CLASSNAME_PREFIX,
|
||||
@@ -39,7 +39,7 @@ import { ConnectedComment as Comment } from 'components/comment/connected-commen
|
||||
import { uploadImage, getPreview } from 'common/api';
|
||||
import { isUserAnonymous } from 'utils/isUserAnonymous';
|
||||
import { bindActions } from 'utils/actionBinder';
|
||||
import { postMessage, parseMessage } from 'utils/postMessage';
|
||||
import { postMessageToParent, parseMessage } from 'utils/postMessage';
|
||||
import { useActions } from 'hooks/useAction';
|
||||
import { setCollapse } from 'store/thread/actions';
|
||||
import { logout } from 'components/auth/auth.api';
|
||||
@@ -122,7 +122,7 @@ export class Root extends Component<Props, State> {
|
||||
const userloading = this.props.fetchUser().finally(() => this.setState({ isUserLoading: false }));
|
||||
|
||||
Promise.all([userloading, this.props.fetchComments()]).finally(() => {
|
||||
postMessage({ remarkIframeHeight: document.body.offsetHeight });
|
||||
postMessageToParent({ height: document.body.offsetHeight });
|
||||
setTimeout(this.checkUrlHash);
|
||||
window.addEventListener('hashchange', this.checkUrlHash);
|
||||
});
|
||||
@@ -169,7 +169,7 @@ export class Root extends Component<Props, State> {
|
||||
toMessage = (hash: string) => {
|
||||
const comment = document.querySelector(hash);
|
||||
if (comment) {
|
||||
postMessage({ scrollTo: comment.getBoundingClientRect().top });
|
||||
postMessageToParent({ scrollTo: comment.getBoundingClientRect().top });
|
||||
comment.classList.add('comment_highlighting');
|
||||
setTimeout(() => {
|
||||
comment.classList.remove('comment_highlighting');
|
||||
@@ -177,14 +177,14 @@ export class Root extends Component<Props, State> {
|
||||
}
|
||||
};
|
||||
|
||||
onMessage(event: MessageEvent<{ theme?: Theme }>) {
|
||||
try {
|
||||
const data = parseMessage(event);
|
||||
onMessage(event: MessageEvent) {
|
||||
const data = parseMessage(event);
|
||||
|
||||
if (data.theme && THEMES.includes(data.theme)) {
|
||||
this.props.setTheme(data.theme);
|
||||
}
|
||||
} catch (e) {}
|
||||
if (!data.theme || !THEMES.includes(data.theme)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.setTheme(data.theme);
|
||||
}
|
||||
|
||||
onBlockedUsersShow = async () => {
|
||||
|
||||
@@ -6,9 +6,9 @@ import { useIntl, defineMessages, FormattedMessage, IntlShape } from 'react-intl
|
||||
import { StoreState } from 'store';
|
||||
import { Comment } from 'common/types';
|
||||
import { fetchInfo } from 'store/user-info/actions';
|
||||
import { userInfo } from 'common/user-info-settings';
|
||||
import { postMessage } from 'utils/postMessage';
|
||||
import { parseQuery } from 'utils/parseQuery';
|
||||
import { bindActions } from 'utils/actionBinder';
|
||||
import { postMessageToParent } from 'utils/postMessage';
|
||||
import { useActions } from 'hooks/useAction';
|
||||
import { Avatar } from 'components/avatar';
|
||||
|
||||
@@ -23,6 +23,8 @@ const messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
const user = parseQuery();
|
||||
|
||||
type Props = {
|
||||
comments: Comment[] | null;
|
||||
} & typeof boundActions & { intl: IntlShape };
|
||||
@@ -38,7 +40,7 @@ class UserInfo extends Component<Props, State> {
|
||||
componentWillMount(): void {
|
||||
if (!this.props.comments && this.state.isLoading) {
|
||||
this.props
|
||||
.fetchInfo()
|
||||
.fetchInfo(user.id)
|
||||
.then(() => {
|
||||
this.setState({ isLoading: false });
|
||||
})
|
||||
@@ -55,7 +57,6 @@ class UserInfo extends Component<Props, State> {
|
||||
}
|
||||
|
||||
render(): JSX.Element | null {
|
||||
const user = userInfo;
|
||||
const { comments = [] } = this.props;
|
||||
const { isLoading } = this.state;
|
||||
|
||||
@@ -87,12 +88,12 @@ class UserInfo extends Component<Props, State> {
|
||||
static onKeyDown(e: KeyboardEvent): void {
|
||||
// ESCAPE key pressed
|
||||
if (e.keyCode === 27) {
|
||||
postMessage({ isUserInfoShown: false });
|
||||
postMessageToParent({ profile: null });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const commentsSelector = (state: StoreState) => state.userComments[userInfo.id];
|
||||
const commentsSelector = (state: StoreState) => state.userComments[user.id];
|
||||
|
||||
export const ConnectedUserInfo: FunctionComponent = () => {
|
||||
const comments = useSelector(commentsSelector);
|
||||
|
||||
+32
-36
@@ -1,6 +1,6 @@
|
||||
import type { UserInfo, Theme } from 'common/types';
|
||||
import { BASE_URL, NODE_ID, COMMENT_NODE_CLASSNAME_PREFIX } from 'common/constants.config';
|
||||
import { parseMessage, ParentMessage } from 'utils/postMessage';
|
||||
import { parseMessage, postMessageToIframe } from 'utils/postMessage';
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
@@ -264,59 +264,55 @@ function createInstance(config: typeof window.remark_config) {
|
||||
},
|
||||
};
|
||||
|
||||
function receiveMessages(event: MessageEvent<ParentMessage>): void {
|
||||
try {
|
||||
const data = parseMessage(event);
|
||||
function receiveMessages(event: MessageEvent): void {
|
||||
const data = parseMessage(event);
|
||||
|
||||
if (data.remarkIframeHeight) {
|
||||
iframe.style.height = `${data.remarkIframeHeight}px`;
|
||||
}
|
||||
if (data.height) {
|
||||
iframe.style.height = `${data.height}px`;
|
||||
}
|
||||
|
||||
if (data.scrollTo) {
|
||||
window.scrollTo(window.pageXOffset, data.scrollTo + iframe.getBoundingClientRect().top + window.pageYOffset);
|
||||
}
|
||||
if (data.scrollTo) {
|
||||
window.scrollTo(window.pageXOffset, data.scrollTo + iframe.getBoundingClientRect().top + window.pageYOffset);
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(data, 'isUserInfoShown')) {
|
||||
if (data.isUserInfoShown) {
|
||||
userInfo.init(data.user);
|
||||
} else {
|
||||
userInfo.close();
|
||||
}
|
||||
if (typeof data.profile === 'object') {
|
||||
if (data.profile === null) {
|
||||
userInfo.close();
|
||||
} else {
|
||||
userInfo.init(data.profile);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.inited) {
|
||||
postHashToIframe();
|
||||
postTitleToIframe(document.title);
|
||||
}
|
||||
} catch (e) {}
|
||||
if (data.inited) {
|
||||
postHashToIframe();
|
||||
postTitleToIframe(document.title);
|
||||
}
|
||||
}
|
||||
|
||||
function postHashToIframe(e?: Event & { newURL: string }) {
|
||||
const hash = e ? `#${e.newURL.split('#')[1]}` : window.location.hash;
|
||||
function postHashToIframe(evt?: Event & { newURL: string }) {
|
||||
const hash = evt ? `#${evt.newURL.split('#')[1]}` : window.location.hash;
|
||||
|
||||
if (hash.indexOf(`#${COMMENT_NODE_CLASSNAME_PREFIX}`) === 0) {
|
||||
if (e) e.preventDefault();
|
||||
|
||||
iframe.contentWindow!.postMessage({ hash }, '*');
|
||||
if (!hash.startsWith(`#${COMMENT_NODE_CLASSNAME_PREFIX}`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
evt?.preventDefault();
|
||||
postMessageToIframe(iframe, { hash });
|
||||
}
|
||||
|
||||
function postTitleToIframe(title: string) {
|
||||
if (iframe.contentWindow) {
|
||||
iframe.contentWindow.postMessage({ title }, '*');
|
||||
}
|
||||
postMessageToIframe(iframe, { title });
|
||||
}
|
||||
|
||||
function postClickOutsideToIframe(e: MouseEvent) {
|
||||
if (iframe.contentWindow && !iframe.contains(e.target as Node)) {
|
||||
iframe.contentWindow.postMessage({ clickOutside: true }, '*');
|
||||
function postClickOutsideToIframe(evt: MouseEvent) {
|
||||
if (iframe.contains(evt.target as Node)) {
|
||||
return;
|
||||
}
|
||||
postMessageToIframe(iframe, { clickOutside: true });
|
||||
}
|
||||
|
||||
function changeTheme(theme: Theme) {
|
||||
if (iframe.contentWindow) {
|
||||
iframe.contentWindow.postMessage({ theme }, '*');
|
||||
}
|
||||
postMessageToIframe(iframe, { theme });
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
|
||||
@@ -31,7 +31,7 @@ async function init(): Promise<void> {
|
||||
throw new Error("Remark42: Can't find root node.");
|
||||
}
|
||||
|
||||
const params = parseQuery<{ page?: string; locale?: string; simple_view?: boolean }>();
|
||||
const params = parseQuery();
|
||||
const locale = getLocale(params);
|
||||
const messages = await loadLocale(locale).catch(() => ({}));
|
||||
const boundActions = bindActionCreators({ fetchHiddenUsers, restoreCollapsedThreads }, store.dispatch);
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import { getUserComments } from 'common/api';
|
||||
import { Comment } from 'common/types';
|
||||
import { userInfo } from 'common/user-info-settings';
|
||||
|
||||
import { StoreAction } from '../index';
|
||||
import { USER_INFO_SET } from './types';
|
||||
|
||||
export const fetchInfo = (): StoreAction<Promise<Comment[] | null>> => async (dispatch) => {
|
||||
if (!userInfo.id) {
|
||||
return null;
|
||||
}
|
||||
export const fetchInfo = (id: string): StoreAction<Promise<Comment[] | null>> => async (dispatch) => {
|
||||
// TODO: limit
|
||||
const info = await getUserComments(userInfo.id, 10);
|
||||
const info = await getUserComments(id, 10);
|
||||
|
||||
dispatch({
|
||||
type: USER_INFO_SET,
|
||||
id: userInfo.id,
|
||||
id,
|
||||
comments: info.comments,
|
||||
});
|
||||
return info.comments;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** converts window.location.search into object */
|
||||
|
||||
export function parseQuery<T extends {}>(search: string = window.location.search): T {
|
||||
export function parseQuery<T extends Record<string, string>>(search: string = window.location.search): T {
|
||||
const params: { [key: string]: string } = {};
|
||||
new URLSearchParams(search).forEach((value: string, key: string) => {
|
||||
params[key] = value;
|
||||
|
||||
@@ -1,42 +1,61 @@
|
||||
import type { Theme, UserInfo } from 'common/types';
|
||||
|
||||
export type ParentMessage = {
|
||||
type ParentMessage = {
|
||||
inited?: true;
|
||||
scrollTo?: number;
|
||||
remarkIframeHeight?: number;
|
||||
} & (
|
||||
| { isUserInfoShown: true; user: UserInfo }
|
||||
| { isUserInfoShown: false; user?: never }
|
||||
| { isUserInfoShown?: never; user?: never }
|
||||
);
|
||||
height?: number;
|
||||
profile?: UserInfo | null;
|
||||
};
|
||||
|
||||
export type ChildMessage = {
|
||||
type ChildMessage = {
|
||||
clickOutside?: true;
|
||||
hash?: string;
|
||||
title?: string;
|
||||
theme?: Theme;
|
||||
};
|
||||
|
||||
type AllMessages = ParentMessage & ChildMessage;
|
||||
type AllMessages = ChildMessage & ParentMessage;
|
||||
|
||||
/**
|
||||
* Sends message to parent window
|
||||
*
|
||||
* @returns request success of fail
|
||||
*/
|
||||
export function postMessage(data: AllMessages): boolean {
|
||||
if (!window.parent || window.parent === window) return false;
|
||||
export function postMessageToParent(data: ParentMessage): boolean {
|
||||
if (!window.parent || window.parent === window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
window.parent.postMessage(data, '*');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends message to target iframe
|
||||
*
|
||||
* @param target iframe to send data
|
||||
* @param data that will be send to iframe
|
||||
* @returns request success of fail
|
||||
*/
|
||||
export function postMessageToIframe(target: HTMLIFrameElement, data: ChildMessage): boolean {
|
||||
if (!target?.contentWindow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
target.contentWindow.postMessage(data, '*');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses data from post message that was received in iframe
|
||||
*
|
||||
* @param evt post message event
|
||||
* @returns
|
||||
*/
|
||||
export function parseMessage<T>({ data }: MessageEvent<T>): T {
|
||||
export function parseMessage({ data }: MessageEvent): AllMessages {
|
||||
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
||||
return {} as T;
|
||||
return {} as AllMessages;
|
||||
}
|
||||
|
||||
return data as T;
|
||||
return data as AllMessages;
|
||||
}
|
||||
|
||||
@@ -127,20 +127,7 @@
|
||||
</script>
|
||||
<% } %>
|
||||
<script>
|
||||
// TODO: we must stop using `setInterval` for resize iframe
|
||||
// TODO: we must use more efficient way to change iframe height
|
||||
// TODO: we must open comments widget and user-info widget on different pages (now iframe.html opens both of widgets)
|
||||
var lastHeight = 0;
|
||||
setInterval(function () {
|
||||
if (document.body.offsetHeight !== lastHeight && document.body.offsetHeight > 22) {
|
||||
lastHeight = document.body.offsetHeight;
|
||||
window.parent.postMessage({ remarkIframeHeight: lastHeight }, '*');
|
||||
}
|
||||
}, 200);
|
||||
|
||||
window.addEventListener('message', receiveMessages);
|
||||
|
||||
function receiveMessages(event) {
|
||||
window.addEventListener('message', function receiveMessages(event) {
|
||||
try {
|
||||
const data = event.data;
|
||||
const isObj = typeof data === 'object' && data !== null && !Array.isArray(data);
|
||||
@@ -155,7 +142,7 @@
|
||||
document.title = data.title;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
|
||||
var remark_config =
|
||||
window.location.search.length < 2
|
||||
|
||||
Reference in New Issue
Block a user