Small refactoring (#609)
* Move visibility param from redux store to local state * use func for parsing location.search * Remove bad wrapper * we already have wrapper with NODE_ID on page and we shouldn't another one with the same id on page * move common part of markup to level up * Tinny refac of setSorting * add dummy types for pollyfils also, a bit rewrited way to resolve imports * Move sort flag to local store Because we don't need to share this param between diffrent parts of interface * Add action creators * move action to action creators * Remove unused functions * Merge in conditions in one * One way for export API methods * add default tags * Rewrited changing read only mode * removed unused function * set sort changes * always send sort from store * rollback if sort don't work * constanst * add typing * shorten export * remove double define of host
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
declare module 'intersection-observer';
|
||||
declare module 'whatwg-fetch';
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
import { Theme } from '@app/common/types';
|
||||
import { CommentsConfig } from '@app/common/config-types';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
remark_config: CommentsConfig;
|
||||
REMARK42: {
|
||||
changeTheme(theme: Theme): void;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -292,31 +292,3 @@ export const emailConfirmationForSubscribe = (token: string) =>
|
||||
* Decline current subscription to updates
|
||||
*/
|
||||
export const unsubscribeFromEmailUpdates = () => fetcher.delete({ url: `/email`, withCredentials: true });
|
||||
|
||||
export default {
|
||||
logIn,
|
||||
logOut,
|
||||
getConfig,
|
||||
getPostComments,
|
||||
getCommentsCount,
|
||||
getComment,
|
||||
getUserComments,
|
||||
putCommentVote,
|
||||
addComment,
|
||||
updateComment,
|
||||
removeMyComment,
|
||||
getUser,
|
||||
getPreview,
|
||||
|
||||
pinComment,
|
||||
unpinComment,
|
||||
setVerifyStatus: setVerifiedStatus,
|
||||
removeVerifyStatus: removeVerifiedStatus,
|
||||
removeComment,
|
||||
blockUser,
|
||||
unblockUser,
|
||||
getBlocked,
|
||||
disableComments,
|
||||
enableComments,
|
||||
uploadImage,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
export const BASE_URL: string =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
((window as any).remark_config && (window as any).remark_config.host) || process.env.REMARK_URL!;
|
||||
export const NODE_ID: string = process.env.REMARK_NODE!;
|
||||
export const BASE_URL = (window.remark_config && window.remark_config.host) || process.env.REMARK_URL!;
|
||||
export const NODE_ID = process.env.REMARK_NODE!;
|
||||
export const API_BASE = '/api/v1';
|
||||
export const COMMENT_NODE_CLASSNAME_PREFIX = 'remark42__comment-';
|
||||
export const COUNTER_NODE_CLASSNAME = 'remark42__counter';
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { Sorting, AuthProvider, Theme } from './types';
|
||||
import * as configConstant from './constants.config';
|
||||
|
||||
export const BASE_URL = configConstant.BASE_URL;
|
||||
export const API_BASE = configConstant.API_BASE;
|
||||
export const NODE_ID = configConstant.NODE_ID;
|
||||
export const COMMENT_NODE_CLASSNAME_PREFIX = configConstant.COMMENT_NODE_CLASSNAME_PREFIX;
|
||||
export { BASE_URL, API_BASE, NODE_ID, COMMENT_NODE_CLASSNAME_PREFIX } from './constants.config';
|
||||
export const LAST_COMMENTS_NODE_CLASSNAME = 'remark42__last-comments';
|
||||
export const MAX_SHOWN_ROOT_COMMENTS = 10;
|
||||
|
||||
@@ -28,8 +24,8 @@ export const LS_COLLAPSE_KEY = '__remarkCollapsed';
|
||||
/** locastorage key for hidden users */
|
||||
export const LS_HIDDEN_USERS_KEY = '__remarkHiddenUsers';
|
||||
|
||||
/** cookie key under which sort preference resides */
|
||||
export const COOKIE_SORT_KEY = 'remarkSort';
|
||||
/** localstorage key under which sort preference resides */
|
||||
export const LS_SORT_KEY = '__remarkSort';
|
||||
|
||||
export const THEMES: Theme[] = ['light', 'dark'];
|
||||
|
||||
|
||||
@@ -5,27 +5,27 @@ import '@webcomponents/custom-elements';
|
||||
import './closest-polyfill';
|
||||
|
||||
export default async function loadPolyfills() {
|
||||
const fillCoreJs = async () => {
|
||||
function fillCoreJs() {
|
||||
if (
|
||||
'startsWith' in String.prototype &&
|
||||
'endsWith' in String.prototype &&
|
||||
'includes' in Array.prototype &&
|
||||
'assign' in Object &&
|
||||
'keys' in Object
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await import(/* webpackChunkName: "core-js" */ 'core-js').then();
|
||||
return;
|
||||
};
|
||||
return import(/* webpackChunkName: "core-js" */ 'core-js');
|
||||
}
|
||||
|
||||
const fillFetch = async () => {
|
||||
function fillFetch() {
|
||||
if ('fetch' in window) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await import(/* webpackChunkName: "whatwg-fetch" */ 'whatwg-fetch' as any).then();
|
||||
};
|
||||
|
||||
const fillIntersectionObserver = async () => {
|
||||
return import(/* webpackChunkName: "whatwg-fetch" */ 'whatwg-fetch');
|
||||
}
|
||||
|
||||
function fillIntersectionObserver() {
|
||||
if (
|
||||
'IntersectionObserver' in window &&
|
||||
'IntersectionObserverEntry' in window &&
|
||||
@@ -34,9 +34,8 @@ export default async function loadPolyfills() {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await import(/* webpackChunkName: "intersection-observer" */ 'intersection-observer' as any).then();
|
||||
};
|
||||
return import(/* webpackChunkName: "intersection-observer" */ 'intersection-observer');
|
||||
}
|
||||
|
||||
await Promise.all([fillCoreJs(), fillFetch(), fillIntersectionObserver()]);
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Theme } from './types';
|
||||
import { THEMES, MAX_SHOWN_ROOT_COMMENTS } from './constants';
|
||||
import parseQuery from '@app/utils/parseQuery';
|
||||
|
||||
export interface QuerySettingsType {
|
||||
site_id?: string;
|
||||
@@ -11,15 +12,7 @@ export interface QuerySettingsType {
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export const querySettings: Partial<QuerySettingsType> =
|
||||
window.location.search
|
||||
.substr(1)
|
||||
.split('&')
|
||||
.reduce<{ [key: string]: string }>((acc, param) => {
|
||||
const pair = param.split('=');
|
||||
acc[pair[0]] = decodeURIComponent(pair[1]);
|
||||
return acc;
|
||||
}, {}) || {};
|
||||
export const querySettings: Partial<QuerySettingsType> = parseQuery();
|
||||
|
||||
if (querySettings.max_shown_comments) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
import { UserInfo } from './types';
|
||||
import parseQuery from '@app/utils/parseQuery';
|
||||
|
||||
export const userInfo: Partial<UserInfo> =
|
||||
window.location.search
|
||||
.substr(1)
|
||||
.split('&')
|
||||
.reduce<{ [key: string]: string }>((acc, param) => {
|
||||
const pair = param.split('=');
|
||||
acc[pair[0]] = decodeURIComponent(pair[1]);
|
||||
return acc;
|
||||
}, {}) || {};
|
||||
export const userInfo: Partial<UserInfo> = parseQuery();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (((userInfo.isDefaultPicture as any) as string) !== '1') {
|
||||
userInfo.isDefaultPicture = false;
|
||||
} else {
|
||||
userInfo.isDefaultPicture = true;
|
||||
}
|
||||
|
||||
export const isDefaultPicture = ((userInfo.isDefaultPicture as any) as string) !== '1';
|
||||
export const id = userInfo.id;
|
||||
export const name = userInfo.name;
|
||||
export const isDefaultPicture = userInfo.isDefaultPicture;
|
||||
export const picture = userInfo.picture;
|
||||
|
||||
@@ -32,8 +32,7 @@ interface PropsWithoutIntl {
|
||||
onSortChange(s: Sorting): Promise<void>;
|
||||
onSignIn(p: AuthProvider): Promise<User | null>;
|
||||
onSignOut(): Promise<void>;
|
||||
onCommentsEnable(): Promise<boolean>;
|
||||
onCommentsDisable(): Promise<boolean>;
|
||||
onCommentsChangeReadOnlyMode(readOnly: boolean): Promise<void>;
|
||||
onBlockedUsersShow(): void;
|
||||
onBlockedUsersHide(): void;
|
||||
}
|
||||
@@ -76,7 +75,6 @@ export class AuthPanel extends Component<Props, State> {
|
||||
};
|
||||
|
||||
this.toggleBlockedVisibility = this.toggleBlockedVisibility.bind(this);
|
||||
this.toggleCommentsAvailability = this.toggleCommentsAvailability.bind(this);
|
||||
this.onSortChange = this.onSortChange.bind(this);
|
||||
this.onSignIn = this.onSignIn.bind(this);
|
||||
this.onEmailSignIn = this.onEmailSignIn.bind(this);
|
||||
@@ -108,9 +106,7 @@ export class AuthPanel extends Component<Props, State> {
|
||||
}
|
||||
|
||||
onSortChange(e: Event) {
|
||||
if (this.props.onSortChange) {
|
||||
this.props.onSortChange((e.target! as HTMLOptionElement).value as Sorting);
|
||||
}
|
||||
this.props.onSortChange((e.target! as HTMLOptionElement).value as Sorting);
|
||||
}
|
||||
|
||||
onSortFocus = () => {
|
||||
@@ -131,13 +127,9 @@ export class AuthPanel extends Component<Props, State> {
|
||||
this.setState({ isBlockedVisible: !this.state.isBlockedVisible });
|
||||
}
|
||||
|
||||
toggleCommentsAvailability() {
|
||||
if (this.props.isCommentsDisabled) {
|
||||
this.props.onCommentsEnable && this.props.onCommentsEnable();
|
||||
} else {
|
||||
this.props.onCommentsDisable && this.props.onCommentsDisable();
|
||||
}
|
||||
}
|
||||
toggleCommentsAvailability = () => {
|
||||
this.props.onCommentsChangeReadOnlyMode(!this.props.isCommentsDisabled);
|
||||
};
|
||||
|
||||
toggleUserInfoVisibility() {
|
||||
const user = this.props.user;
|
||||
@@ -368,7 +360,7 @@ export class AuthPanel extends Component<Props, State> {
|
||||
<Button
|
||||
kind="link"
|
||||
mix="auth-panel__admin-action"
|
||||
{...getHandleClickProps(() => this.toggleCommentsAvailability())}
|
||||
{...getHandleClickProps(this.toggleCommentsAvailability)}
|
||||
role="link"
|
||||
>
|
||||
{isCommentsDisabled ? (
|
||||
@@ -384,6 +376,7 @@ export class AuthPanel extends Component<Props, State> {
|
||||
const { sort } = this.props;
|
||||
const { sortSelectFocused } = this.state;
|
||||
const sortArray = getSortArray(sort, this.props.intl);
|
||||
|
||||
return (
|
||||
<span className="auth-panel__sort">
|
||||
<FormattedMessage id="commentSort.sort-by" defaultMessage="Sort by" />{' '}
|
||||
|
||||
@@ -252,7 +252,7 @@ class Comment extends Component<Props, State> {
|
||||
: intl.formatMessage(messages.unverifyUser, { userName });
|
||||
|
||||
if (confirm(promptMessage)) {
|
||||
this.props.setVerifyStatus!(userId, value);
|
||||
this.props.setVerifiedStatus!(userId, value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export const boundActions = bindActions({
|
||||
blockUser,
|
||||
unblockUser,
|
||||
hideUser,
|
||||
setVerifyStatus: setVerifiedStatus,
|
||||
setVerifiedStatus,
|
||||
});
|
||||
|
||||
export const ConnectedComment: FunctionComponent<Omit<Props, keyof (ProvidedProps & typeof bindActions)>> = props => {
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
/** @jsx createElement */
|
||||
import { createElement, Component, FunctionComponent } from 'preact';
|
||||
import { createElement, Component, FunctionComponent, Fragment } from 'preact';
|
||||
import { useSelector } from 'react-redux';
|
||||
import b from 'bem-react-helper';
|
||||
import { IntlShape, useIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||
|
||||
import { User, Sorting, AuthProvider } from '@app/common/types';
|
||||
import {
|
||||
NODE_ID,
|
||||
COMMENT_NODE_CLASSNAME_PREFIX,
|
||||
MAX_SHOWN_ROOT_COMMENTS,
|
||||
THEMES,
|
||||
IS_MOBILE,
|
||||
LS_SORT_KEY,
|
||||
DEFAULT_SORT,
|
||||
} from '@app/common/constants';
|
||||
import { maxShownComments } from '@app/common/settings';
|
||||
|
||||
@@ -23,14 +24,12 @@ import {
|
||||
blockUser,
|
||||
unblockUser,
|
||||
fetchBlockedUsers,
|
||||
setSettingsVisibility,
|
||||
hideUser,
|
||||
unhideUser,
|
||||
} from '@app/store/user/actions';
|
||||
import { fetchComments } from '@app/store/comments/actions';
|
||||
import { setCommentsReadOnlyState } from '@app/store/post_info/actions';
|
||||
import { setTheme } from '@app/store/theme/actions';
|
||||
import { setSort } from '@app/store/sort/actions';
|
||||
import { addComment, updateComment } from '@app/store/comments/actions';
|
||||
|
||||
import { AuthPanel } from '@app/components/auth-panel';
|
||||
@@ -45,11 +44,10 @@ import { isUserAnonymous } from '@app/utils/isUserAnonymous';
|
||||
import { bindActions } from '@app/utils/actionBinder';
|
||||
import postMessage from '@app/utils/postMessage';
|
||||
import { useActions } from '@app/hooks/useAction';
|
||||
import * as localStorage from '@app/common/local-storage';
|
||||
|
||||
const mapStateToProps = (state: StoreState) => ({
|
||||
user: state.user,
|
||||
sort: state.sort,
|
||||
isSettingsVisible: state.isSettingsVisible,
|
||||
topComments: state.topComments,
|
||||
pinnedComments: state.pinnedComments.map(id => state.comments[id]).filter(c => !c.hidden),
|
||||
provider: state.provider,
|
||||
@@ -65,13 +63,10 @@ const boundActions = bindActions({
|
||||
fetchComments,
|
||||
fetchUser,
|
||||
fetchBlockedUsers,
|
||||
setSettingsVisibility,
|
||||
logIn,
|
||||
logOut: logout,
|
||||
setTheme,
|
||||
enableComments: () => setCommentsReadOnlyState(false),
|
||||
disableComments: () => setCommentsReadOnlyState(true),
|
||||
changeSort: setSort,
|
||||
setCommentsReadOnlyState,
|
||||
blockUser,
|
||||
unblockUser,
|
||||
hideUser,
|
||||
@@ -83,8 +78,10 @@ const boundActions = bindActions({
|
||||
type Props = ReturnType<typeof mapStateToProps> & typeof boundActions & { intl: IntlShape };
|
||||
|
||||
interface State {
|
||||
isLoaded: boolean;
|
||||
sort: string;
|
||||
isUserLoading: boolean;
|
||||
isCommentsListLoading: boolean;
|
||||
isSettingsVisible: boolean;
|
||||
commentsShown: number;
|
||||
wasSomeoneUnblocked: boolean;
|
||||
}
|
||||
@@ -96,30 +93,33 @@ const messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
/** main component fr main comments widget */
|
||||
export class Root extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
function getInitialSort() {
|
||||
const sort = localStorage.getItem(LS_SORT_KEY) as Sorting;
|
||||
|
||||
this.state = {
|
||||
isLoaded: false,
|
||||
isCommentsListLoading: false,
|
||||
commentsShown: maxShownComments,
|
||||
wasSomeoneUnblocked: false,
|
||||
};
|
||||
|
||||
this.onBlockedUsersShow = this.onBlockedUsersShow.bind(this);
|
||||
this.onBlockedUsersHide = this.onBlockedUsersHide.bind(this);
|
||||
this.onUnblockSomeone = this.onUnblockSomeone.bind(this);
|
||||
this.showMore = this.showMore.bind(this);
|
||||
if (sort) {
|
||||
return sort;
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
Promise.all([this.props.fetchUser(), this.props.fetchComments(this.props.sort)]).finally(() => {
|
||||
this.setState({
|
||||
isLoaded: true,
|
||||
});
|
||||
return DEFAULT_SORT;
|
||||
}
|
||||
|
||||
/** main component fr main comments widget */
|
||||
export class Root extends Component<Props, State> {
|
||||
state = {
|
||||
sort: getInitialSort(),
|
||||
isUserLoading: true,
|
||||
isCommentsListLoading: true,
|
||||
commentsShown: maxShownComments,
|
||||
wasSomeoneUnblocked: false,
|
||||
isSettingsVisible: false,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
const userloading = this.props.fetchUser().finally(() => this.setState({ isUserLoading: false }));
|
||||
|
||||
Promise.all([userloading, this.fetchComments()]).finally(() => {
|
||||
postMessage({ remarkIframeHeight: document.body.offsetHeight });
|
||||
this.setState({ isCommentsListLoading: false });
|
||||
setTimeout(this.checkUrlHash);
|
||||
window.addEventListener('hashchange', this.checkUrlHash);
|
||||
});
|
||||
@@ -127,22 +127,38 @@ export class Root extends Component<Props, State> {
|
||||
window.addEventListener('message', this.onMessage.bind(this));
|
||||
}
|
||||
|
||||
logIn = async (p: AuthProvider): Promise<User | null> => {
|
||||
const user = await this.props.logIn(p);
|
||||
await this.props.fetchComments(this.props.sort);
|
||||
fetchComments() {
|
||||
return this.props.fetchComments(this.state.sort);
|
||||
}
|
||||
|
||||
changeSort = async (sort: Sorting) => {
|
||||
if (sort === this.state.sort) return;
|
||||
const prevSort = this.state.sort;
|
||||
|
||||
this.setState({ isCommentsListLoading: true, sort });
|
||||
try {
|
||||
await this.fetchComments();
|
||||
localStorage.setItem(LS_SORT_KEY, sort);
|
||||
this.setState({ isCommentsListLoading: false });
|
||||
} catch (e) {
|
||||
this.setState({ sort: prevSort, isCommentsListLoading: false });
|
||||
}
|
||||
};
|
||||
|
||||
logIn = async (provider: AuthProvider): Promise<User | null> => {
|
||||
const user = await this.props.logIn(provider);
|
||||
|
||||
await this.fetchComments();
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
logOut = async (): Promise<void> => {
|
||||
await this.props.logOut();
|
||||
await this.props.fetchComments(this.props.sort);
|
||||
await this.fetchComments();
|
||||
};
|
||||
|
||||
checkUrlHash(
|
||||
e: Event & {
|
||||
newURL?: string;
|
||||
}
|
||||
) {
|
||||
checkUrlHash(e: Event & { newURL?: string }) {
|
||||
const hash = e ? `#${e.newURL!.split('#')[1]}` : window.location.hash;
|
||||
|
||||
if (hash.indexOf(`#${COMMENT_NODE_CLASSNAME_PREFIX}`) === 0) {
|
||||
@@ -169,40 +185,33 @@ export class Root extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
async onBlockedUsersShow() {
|
||||
onBlockedUsersShow = async () => {
|
||||
if (this.props.user && this.props.user.admin) {
|
||||
await this.props.fetchBlockedUsers();
|
||||
}
|
||||
this.props.setSettingsVisibility(true);
|
||||
}
|
||||
this.setState({ isSettingsVisible: true });
|
||||
};
|
||||
|
||||
async onBlockedUsersHide() {
|
||||
onBlockedUsersHide = async () => {
|
||||
// if someone was unblocked let's reload comments
|
||||
if (this.state.wasSomeoneUnblocked) {
|
||||
this.props.fetchComments(this.props.sort);
|
||||
this.fetchComments();
|
||||
}
|
||||
this.props.setSettingsVisibility(false);
|
||||
this.setState({
|
||||
wasSomeoneUnblocked: false,
|
||||
isSettingsVisible: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
async changeSort(sort: Sorting) {
|
||||
if (sort === this.props.sort) return;
|
||||
this.setState({ isCommentsListLoading: true });
|
||||
await this.props.changeSort(sort).catch(() => {});
|
||||
this.setState({ isCommentsListLoading: false });
|
||||
}
|
||||
|
||||
onUnblockSomeone() {
|
||||
onUnblockSomeone = () => {
|
||||
this.setState({ wasSomeoneUnblocked: true });
|
||||
}
|
||||
};
|
||||
|
||||
showMore() {
|
||||
showMore = () => {
|
||||
this.setState({
|
||||
commentsShown: this.state.commentsShown + MAX_SHOWN_ROOT_COMMENTS,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines whether current client is logged in via `Anonymous provider`
|
||||
@@ -211,44 +220,48 @@ export class Root extends Component<Props, State> {
|
||||
return isUserAnonymous(this.props.user);
|
||||
}
|
||||
|
||||
render(props: Props, { isLoaded, isCommentsListLoading, commentsShown }: State) {
|
||||
if (!isLoaded) {
|
||||
return (
|
||||
<div id={NODE_ID}>
|
||||
<div className={b('root', {}, { theme: props.theme })}>
|
||||
<Preloader mix="root__preloader" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
render(props: Props, { isUserLoading, isCommentsListLoading, commentsShown, isSettingsVisible }: State) {
|
||||
if (isUserLoading) {
|
||||
return <Preloader mix="root__preloader" />;
|
||||
}
|
||||
|
||||
const isGuest = !props.user;
|
||||
const isCommentsDisabled = !!props.info.read_only;
|
||||
const isCommentsDisabled = props.info.read_only!;
|
||||
const imageUploadHandler = this.isAnonymous() ? undefined : this.props.uploadImage;
|
||||
|
||||
return (
|
||||
<div id={NODE_ID}>
|
||||
<div className={b('root', {}, { theme: props.theme })}>
|
||||
<AuthPanel
|
||||
theme={this.props.theme}
|
||||
user={this.props.user}
|
||||
hiddenUsers={this.props.hiddenUsers}
|
||||
sort={this.props.sort}
|
||||
isCommentsDisabled={isCommentsDisabled}
|
||||
postInfo={this.props.info}
|
||||
providers={StaticStore.config.auth_providers}
|
||||
provider={this.props.provider}
|
||||
onSignIn={this.logIn}
|
||||
onSignOut={this.logOut}
|
||||
onBlockedUsersShow={this.onBlockedUsersShow}
|
||||
onBlockedUsersHide={this.onBlockedUsersHide}
|
||||
onCommentsEnable={this.props.enableComments}
|
||||
onCommentsDisable={this.props.disableComments}
|
||||
onSortChange={this.props.changeSort}
|
||||
/>
|
||||
|
||||
{!this.props.isSettingsVisible && (
|
||||
<div className="root__main">
|
||||
<Fragment>
|
||||
<AuthPanel
|
||||
theme={this.props.theme}
|
||||
user={this.props.user}
|
||||
hiddenUsers={this.props.hiddenUsers}
|
||||
sort={this.state.sort}
|
||||
isCommentsDisabled={isCommentsDisabled}
|
||||
postInfo={this.props.info}
|
||||
providers={StaticStore.config.auth_providers}
|
||||
provider={this.props.provider}
|
||||
onSignIn={this.logIn}
|
||||
onSignOut={this.logOut}
|
||||
onBlockedUsersShow={this.onBlockedUsersShow}
|
||||
onBlockedUsersHide={this.onBlockedUsersHide}
|
||||
onCommentsChangeReadOnlyMode={this.props.setCommentsReadOnlyState}
|
||||
onSortChange={this.changeSort}
|
||||
/>
|
||||
<div className="root__main">
|
||||
{isSettingsVisible ? (
|
||||
<Settings
|
||||
intl={this.props.intl}
|
||||
user={this.props.user}
|
||||
hiddenUsers={this.props.hiddenUsers}
|
||||
blockedUsers={this.props.blockedUsers}
|
||||
blockUser={this.props.blockUser}
|
||||
unblockUser={this.props.unblockUser}
|
||||
hideUser={this.props.hideUser}
|
||||
unhideUser={this.props.unhideUser}
|
||||
onUnblockSomeone={this.onUnblockSomeone}
|
||||
/>
|
||||
) : (
|
||||
<Fragment>
|
||||
{!isGuest && !isCommentsDisabled && (
|
||||
<CommentForm
|
||||
intl={this.props.intl}
|
||||
@@ -312,40 +325,10 @@ export class Root extends Component<Props, State> {
|
||||
<Preloader mix="root__preloader" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{this.props.isSettingsVisible && (
|
||||
<div className="root__main">
|
||||
<Settings
|
||||
intl={this.props.intl}
|
||||
user={this.props.user}
|
||||
hiddenUsers={this.props.hiddenUsers}
|
||||
blockedUsers={this.props.blockedUsers}
|
||||
blockUser={this.props.blockUser}
|
||||
unblockUser={this.props.unblockUser}
|
||||
hideUser={this.props.hideUser}
|
||||
unhideUser={this.props.unhideUser}
|
||||
onUnblockSomeone={this.onUnblockSomeone}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="root__copyright" role="contentinfo">
|
||||
<FormattedMessage
|
||||
id="root.powered-by"
|
||||
defaultMessage="Powered by <a>Remark42</a>"
|
||||
values={{
|
||||
a: (title: string) => (
|
||||
<a class="root__copyright-link" href="https://remark42.com/">
|
||||
{title}
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -355,5 +338,23 @@ export const ConnectedRoot: FunctionComponent = () => {
|
||||
const props = useSelector(mapStateToProps);
|
||||
const actions = useActions(boundActions);
|
||||
const intl = useIntl();
|
||||
return <Root {...props} {...actions} intl={intl} />;
|
||||
|
||||
return (
|
||||
<div className={b('root', {}, { theme: props.theme })}>
|
||||
<Root {...props} {...actions} intl={intl} />
|
||||
<p className="root__copyright" role="contentinfo">
|
||||
<FormattedMessage
|
||||
id="root.powered-by"
|
||||
defaultMessage="Powered by <a>Remark42</a>"
|
||||
values={{
|
||||
a: (title: string) => (
|
||||
<a class="root__copyright-link" href="https://remark42.com/">
|
||||
{title}
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+23
-28
@@ -1,11 +1,6 @@
|
||||
/* eslint-disable no-console, @typescript-eslint/no-explicit-any */
|
||||
declare let remark_config: CommentsConfig;
|
||||
|
||||
/* eslint-disable no-console */
|
||||
import { BASE_URL, NODE_ID, COMMENT_NODE_CLASSNAME_PREFIX } from '@app/common/constants.config';
|
||||
import { UserInfo, Theme } from '@app/common/types';
|
||||
import { CommentsConfig } from '@app/common/config-types';
|
||||
|
||||
const HOST = remark_config.host || BASE_URL;
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
@@ -28,7 +23,7 @@ function createFrame({
|
||||
host: string;
|
||||
query: string;
|
||||
height?: string;
|
||||
__colors__?: any;
|
||||
__colors__?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}) {
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.src = `${host}/web/iframe.html?${query}`;
|
||||
@@ -51,8 +46,8 @@ function createFrame({
|
||||
return iframe;
|
||||
}
|
||||
|
||||
function init(): void {
|
||||
const node = document.getElementById(remark_config.node || NODE_ID);
|
||||
function init() {
|
||||
const node = document.getElementById(window.remark_config.node || NODE_ID);
|
||||
|
||||
if (!node) {
|
||||
console.error("Remark42: Can't find root node.");
|
||||
@@ -60,29 +55,33 @@ function init(): void {
|
||||
}
|
||||
|
||||
try {
|
||||
remark_config = remark_config || {};
|
||||
window.remark_config = window.remark_config || {};
|
||||
} catch (e) {
|
||||
console.error('Remark42: Config object is undefined.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!remark_config.site_id) {
|
||||
if (!window.remark_config.site_id) {
|
||||
console.error('Remark42: Site ID is undefined.');
|
||||
return;
|
||||
}
|
||||
|
||||
remark_config.url = (remark_config.url || window.location.origin + window.location.pathname).split('#')[0];
|
||||
window.remark_config.url = (window.remark_config.url || window.location.origin + window.location.pathname).split(
|
||||
'#'
|
||||
)[0];
|
||||
|
||||
(window as any).REMARK42 = (window as any).REMARK42 || {};
|
||||
(window as any).REMARK42.changeTheme = changeTheme;
|
||||
window.REMARK42 = window.REMARK42 || {};
|
||||
window.REMARK42.changeTheme = changeTheme;
|
||||
|
||||
const query = Object.keys(remark_config)
|
||||
.filter((key: any) => key !== `__colors__`)
|
||||
.map((key: any) => {
|
||||
return `${encodeURIComponent(key)}=${encodeURIComponent((remark_config as any)[key])}`;
|
||||
const query = Object.keys(window.remark_config)
|
||||
.filter(key => key !== '__colors__')
|
||||
.map(key => {
|
||||
return `${encodeURIComponent(key)}=${encodeURIComponent(
|
||||
window.remark_config[key as keyof typeof window.remark_config]
|
||||
)}`;
|
||||
})
|
||||
.join('&');
|
||||
const iframe = createFrame({ host: HOST, query, __colors__: remark_config.__colors__ });
|
||||
const iframe = createFrame({ host: BASE_URL, query, __colors__: window.remark_config.__colors__ });
|
||||
|
||||
node.appendChild(iframe);
|
||||
|
||||
@@ -195,7 +194,7 @@ function init(): void {
|
||||
query +
|
||||
'&page=user-info&' +
|
||||
`&id=${user.id}&name=${user.name}&picture=${user.picture || ''}&isDefaultPicture=${user.isDefaultPicture || 0}`;
|
||||
const iframe = createFrame({ host: HOST, query: queryUserInfo, height: '100%' });
|
||||
const iframe = createFrame({ host: BASE_URL, query: queryUserInfo, height: '100%' });
|
||||
this.node.appendChild(iframe);
|
||||
this.iframe = iframe;
|
||||
this.node.appendChild(this.closeEl);
|
||||
@@ -284,11 +283,7 @@ function init(): void {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function postHashToIframe(
|
||||
e?: Event & {
|
||||
newURL: string;
|
||||
}
|
||||
): void {
|
||||
function postHashToIframe(e?: Event & { newURL: string }) {
|
||||
const hash = e ? `#${e.newURL.split('#')[1]}` : window.location.hash;
|
||||
|
||||
if (hash.indexOf(`#${COMMENT_NODE_CLASSNAME_PREFIX}`) === 0) {
|
||||
@@ -298,17 +293,17 @@ function init(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function postTitleToIframe(title: string): void {
|
||||
function postTitleToIframe(title: string) {
|
||||
iframe.contentWindow!.postMessage(JSON.stringify({ title }), '*');
|
||||
}
|
||||
|
||||
function postClickOutsideToIframe(e: MouseEvent): void {
|
||||
function postClickOutsideToIframe(e: MouseEvent) {
|
||||
if (!iframe.contains(e.target as Node)) {
|
||||
iframe.contentWindow!.postMessage(JSON.stringify({ clickOutside: true }), '*');
|
||||
}
|
||||
}
|
||||
|
||||
function changeTheme(theme: Theme): void {
|
||||
function changeTheme(theme: Theme) {
|
||||
iframe.contentWindow!.postMessage(JSON.stringify({ theme }), '*');
|
||||
}
|
||||
}
|
||||
|
||||
+8
-18
@@ -24,10 +24,11 @@ import '@app/components/list-comments';
|
||||
|
||||
import { NODE_ID, BASE_URL } from '@app/common/constants';
|
||||
import { StaticStore } from '@app/common/static_store';
|
||||
import api from '@app/common/api';
|
||||
import { getConfig } from '@app/common/api';
|
||||
import { fetchHiddenUsers } from './store/user/actions';
|
||||
import { restoreProvider } from './store/provider/actions';
|
||||
import { restoreCollapsedThreads } from './store/thread/actions';
|
||||
import parseQuery from './utils/parseQuery';
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
@@ -55,29 +56,18 @@ async function init(): Promise<void> {
|
||||
boundActions.restoreProvider();
|
||||
boundActions.restoreCollapsedThreads();
|
||||
|
||||
const params = window.location.search
|
||||
.replace(/^\?/, '')
|
||||
.split('&')
|
||||
.reduce<{ [key: string]: string }>((memo, value) => {
|
||||
const vals = value.split('=');
|
||||
if (vals.length === 2) {
|
||||
memo[vals[0]] = vals[1];
|
||||
}
|
||||
return memo;
|
||||
}, {});
|
||||
const params = parseQuery();
|
||||
const locale = getLocale(params);
|
||||
const messages = await loadLocale(locale).catch(() => ({}));
|
||||
StaticStore.config = await api.getConfig();
|
||||
StaticStore.config = await getConfig();
|
||||
|
||||
if (params.page === 'user-info') {
|
||||
return render(
|
||||
<IntlProvider locale={locale} messages={messages}>
|
||||
<div id={NODE_ID}>
|
||||
<div className="root root_user-info">
|
||||
<Provider store={reduxStore}>
|
||||
<UserInfo />
|
||||
</Provider>
|
||||
</div>
|
||||
<div className="root root_user-info">
|
||||
<Provider store={reduxStore}>
|
||||
<UserInfo />
|
||||
</Provider>
|
||||
</div>
|
||||
</IntlProvider>,
|
||||
node
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { COMMENTS_ACTIONS } from './comments/types';
|
||||
import { POST_INFO_ACTIONS } from './post_info/types';
|
||||
import { SORT_ACTIONS } from './sort/types';
|
||||
import { THEME_ACTIONS } from './theme/types';
|
||||
import { THREAD_ACTIONS } from './thread/types';
|
||||
import { USER_ACTIONS } from './user/types';
|
||||
@@ -11,7 +10,6 @@ import { PROVIDER_ACTIONS } from './provider/types';
|
||||
export type ACTIONS =
|
||||
| COMMENTS_ACTIONS
|
||||
| POST_INFO_ACTIONS
|
||||
| SORT_ACTIONS
|
||||
| THEME_ACTIONS
|
||||
| THREAD_ACTIONS
|
||||
| USER_ACTIONS
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import api from '@app/common/api';
|
||||
import { Tree, Comment, Sorting, CommentMode, Node } from '@app/common/types';
|
||||
import * as api from '@app/common/api';
|
||||
import { Tree, Comment, CommentMode, Node, Sorting } from '@app/common/types';
|
||||
|
||||
import { StoreAction, StoreState } from '../index';
|
||||
import { POST_INFO_SET } from '../post_info/types';
|
||||
import { setPostInfo } from '../post_info/actions';
|
||||
import { filterTree } from './utils';
|
||||
import { COMMENTS_SET, COMMENT_MODE_SET, COMMENTS_APPEND, COMMENTS_EDIT } from './types';
|
||||
import { COMMENTS_SET, COMMENT_MODE_SET, COMMENTS_APPEND, COMMENTS_EDIT, COMMENT_MODE_SET_ACTION } from './types';
|
||||
|
||||
/** sets comments, and put pinned comments in cache */
|
||||
export const setComments = (comments: Node[]): StoreAction<void> => dispatch => {
|
||||
@@ -68,15 +68,17 @@ export const removeComment = (id: Comment['id']): StoreAction<Promise<void>> =>
|
||||
|
||||
/** fetches comments from server */
|
||||
export const fetchComments = (sort: Sorting): StoreAction<Promise<Tree>> => async (dispatch, getState) => {
|
||||
const { hiddenUsers } = getState();
|
||||
const hiddenUsersIds = Object.keys(hiddenUsers);
|
||||
const data = await api.getPostComments(sort);
|
||||
const hiddenUsersIds = Object.keys(getState().hiddenUsers);
|
||||
if (hiddenUsersIds.length > 0)
|
||||
|
||||
if (hiddenUsersIds.length > 0) {
|
||||
data.comments = filterTree(data.comments, node => hiddenUsersIds.indexOf(node.comment.user.id) === -1);
|
||||
}
|
||||
|
||||
dispatch(setComments(data.comments));
|
||||
dispatch({
|
||||
type: POST_INFO_SET,
|
||||
info: data.info,
|
||||
});
|
||||
dispatch(setPostInfo(data.info));
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -85,16 +87,13 @@ export const setCommentMode = (mode: StoreState['activeComment']): StoreAction<v
|
||||
if (mode !== null && mode.state === CommentMode.None) {
|
||||
mode = null;
|
||||
}
|
||||
dispatch({
|
||||
type: COMMENT_MODE_SET,
|
||||
mode,
|
||||
});
|
||||
dispatch(unsetCommentMode(mode));
|
||||
};
|
||||
|
||||
/** unsets comment mode */
|
||||
export const unsetCommentMode = (): StoreAction<void> => dispatch => {
|
||||
dispatch({
|
||||
export function unsetCommentMode(mode: StoreState['activeComment'] = null) {
|
||||
return {
|
||||
type: COMMENT_MODE_SET,
|
||||
mode: null,
|
||||
});
|
||||
};
|
||||
mode,
|
||||
} as COMMENT_MODE_SET_ACTION;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,9 @@ import { Comment, CommentMode } from '@app/common/types';
|
||||
import { StoreState } from '../index';
|
||||
|
||||
export const getCommentMode = (id: Comment['id']) => (state: StoreState): CommentMode => {
|
||||
if (state.activeComment === null) {
|
||||
return CommentMode.None;
|
||||
}
|
||||
if (state.activeComment.id !== id) {
|
||||
if (state.activeComment === null || state.activeComment.id !== id) {
|
||||
return CommentMode.None;
|
||||
}
|
||||
|
||||
return state.activeComment.state;
|
||||
};
|
||||
|
||||
@@ -1,40 +1,24 @@
|
||||
import { PostInfo } from '@app/common/types';
|
||||
|
||||
import { StoreAction } from '../index';
|
||||
import { POST_INFO_SET } from './types';
|
||||
import api from '@app/common/api';
|
||||
import { POST_INFO_SET, POST_INFO_SET_ACTION } from './types';
|
||||
import { disableComments, enableComments } from '@app/common/api';
|
||||
import { unsetCommentMode } from '../comments/actions';
|
||||
|
||||
export const setPostInfo = (info: PostInfo): StoreAction<void> => dispatch =>
|
||||
dispatch({
|
||||
export function setPostInfo(info: PostInfo) {
|
||||
return {
|
||||
type: POST_INFO_SET,
|
||||
info,
|
||||
});
|
||||
} as POST_INFO_SET_ACTION;
|
||||
}
|
||||
|
||||
/** set state of post: readonly or not */
|
||||
export const setCommentsReadOnlyState = (state: boolean): StoreAction<Promise<boolean>> => async (
|
||||
dispatch,
|
||||
getState
|
||||
) => {
|
||||
await (!state ? api.enableComments() : api.disableComments());
|
||||
const storeState = getState();
|
||||
dispatch(unsetCommentMode());
|
||||
dispatch({
|
||||
type: POST_INFO_SET,
|
||||
info: { ...storeState.info, read_only: state },
|
||||
});
|
||||
return state;
|
||||
};
|
||||
export function setCommentsReadOnlyState(read_only: boolean): StoreAction<Promise<void>> {
|
||||
return async (dispatch, getState) => {
|
||||
const { info } = getState();
|
||||
|
||||
/** toggles state of post: readonly or not */
|
||||
export const toggleCommentsReadOnlyState = (): StoreAction<Promise<boolean>> => async (dispatch, getState) => {
|
||||
const storeState = getState();
|
||||
const state = !storeState.info.read_only!;
|
||||
await (state ? api.enableComments() : api.disableComments());
|
||||
dispatch(unsetCommentMode());
|
||||
dispatch({
|
||||
type: POST_INFO_SET,
|
||||
info: { ...storeState.info, read_only: !state },
|
||||
});
|
||||
return !state;
|
||||
};
|
||||
await (read_only ? disableComments() : enableComments());
|
||||
dispatch(unsetCommentMode());
|
||||
dispatch(setPostInfo({ ...info, read_only }));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,11 +7,4 @@ export interface POST_INFO_SET_ACTION {
|
||||
info: PostInfo;
|
||||
}
|
||||
|
||||
export const POST_INFO_SET_READONLY = 'COMMENTS/SET_READONLY';
|
||||
|
||||
export interface POST_INFO_SET_READONLY_ACTION {
|
||||
type: typeof POST_INFO_SET_READONLY;
|
||||
readonly: boolean;
|
||||
}
|
||||
|
||||
export type POST_INFO_ACTIONS = POST_INFO_SET_ACTION | POST_INFO_SET_READONLY_ACTION;
|
||||
export type POST_INFO_ACTIONS = POST_INFO_SET_ACTION;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import comments from './comments/reducers';
|
||||
import postinfo from './post_info/reducers';
|
||||
import sort from './sort/reducers';
|
||||
import theme from './theme/reducers';
|
||||
import user from './user/reducers';
|
||||
import userInfo from './user-info/reducers';
|
||||
@@ -11,7 +10,6 @@ import provider from './provider/reducers';
|
||||
export default {
|
||||
...comments,
|
||||
...postinfo,
|
||||
...sort,
|
||||
...theme,
|
||||
...user,
|
||||
...userInfo,
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Sorting } from '@app/common/types';
|
||||
import { COOKIE_SORT_KEY } from '@app/common/constants';
|
||||
import { setCookie } from '@app/common/cookies';
|
||||
|
||||
import { StoreAction } from '../index';
|
||||
import { fetchComments } from '../comments/actions';
|
||||
import { SORT_SET, SORT_SET_ACTION } from './types';
|
||||
|
||||
function setSortCookie(sort: Sorting) {
|
||||
try {
|
||||
setCookie(COOKIE_SORT_KEY, sort, { expires: 60 * 60 * 24 * 365, path: '/' }); // save sorting for a year
|
||||
} catch {
|
||||
// can't save; ignore it
|
||||
}
|
||||
}
|
||||
|
||||
export const setSort = (sort: Sorting): StoreAction<Promise<void>> => async (dispatch, getState) => {
|
||||
const originalSort = getState().sort;
|
||||
setSortCookie(sort);
|
||||
|
||||
try {
|
||||
const action: SORT_SET_ACTION = {
|
||||
type: SORT_SET,
|
||||
sort,
|
||||
};
|
||||
|
||||
await dispatch(action);
|
||||
await dispatch(fetchComments(sort));
|
||||
} catch {
|
||||
// restore sort in case of error, probably network error
|
||||
|
||||
const action: SORT_SET_ACTION = {
|
||||
type: SORT_SET,
|
||||
sort: originalSort,
|
||||
};
|
||||
|
||||
setSortCookie(originalSort);
|
||||
await dispatch(action);
|
||||
}
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Sorting } from '@app/common/types';
|
||||
import { COOKIE_SORT_KEY, DEFAULT_SORT } from '@app/common/constants';
|
||||
import { getCookie } from '@app/common/cookies';
|
||||
|
||||
import { SORT_SET, SORT_SET_ACTION } from './types';
|
||||
|
||||
const getDefaultSort = (): Sorting => {
|
||||
try {
|
||||
return (getCookie(COOKIE_SORT_KEY) as Sorting) || DEFAULT_SORT;
|
||||
} catch (e) {
|
||||
return DEFAULT_SORT;
|
||||
}
|
||||
};
|
||||
|
||||
export const sort = (state: Sorting = getDefaultSort(), action: SORT_SET_ACTION): Sorting => {
|
||||
switch (action.type) {
|
||||
case SORT_SET: {
|
||||
return action.sort;
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default { sort };
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Sorting } from '@app/common/types';
|
||||
|
||||
export const SORT_SET = 'SORT/SET';
|
||||
|
||||
export interface SORT_SET_ACTION {
|
||||
type: typeof SORT_SET;
|
||||
sort: Sorting;
|
||||
}
|
||||
|
||||
export type SORT_ACTIONS = SORT_SET_ACTION;
|
||||
@@ -1,27 +1,16 @@
|
||||
import api from '@app/common/api';
|
||||
import { Comment, User } from '@app/common/types';
|
||||
import { getUserComments } from '@app/common/api';
|
||||
import { Comment } from '@app/common/types';
|
||||
import { userInfo } from '@app/common/user-info-settings';
|
||||
|
||||
import { StoreAction } from '../index';
|
||||
import { USER_INFO_SET } from './types';
|
||||
|
||||
export const getUserComments = (userId: User['id']): StoreAction<Comment[] | null> => (_dispatch, getState) =>
|
||||
getState().userComments![userId] || null;
|
||||
|
||||
export const getComments = (id: User['id']): StoreAction<Comment[] | null> => (_dispatch, getState) => {
|
||||
const comments = getState().userComments![id];
|
||||
if (comments) {
|
||||
return comments;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const fetchInfo = (): StoreAction<Promise<Comment[] | null>> => async dispatch => {
|
||||
if (!userInfo.id) {
|
||||
return null;
|
||||
}
|
||||
// TODO: limit
|
||||
const info = await api.getUserComments(userInfo.id, 10);
|
||||
const info = await getUserComments(userInfo.id, 10);
|
||||
dispatch({
|
||||
type: USER_INFO_SET,
|
||||
id: userInfo.id,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import api from '@app/common/api';
|
||||
import * as api from '@app/common/api';
|
||||
import { User, BlockedUser, AuthProvider, BlockTTL } from '@app/common/types';
|
||||
import { ttlToTime } from '@app/utils/ttl-to-time';
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
USER_HIDE,
|
||||
USER_UNHIDE,
|
||||
USER_SUBSCRIPTION_SET,
|
||||
SETTINGS_VISIBLE_SET,
|
||||
USER_SET_ACTION,
|
||||
} from './types';
|
||||
import { unsetCommentMode } from '../comments/actions';
|
||||
import { IS_STORAGE_AVAILABLE, LS_HIDDEN_USERS_KEY } from '@app/common/constants';
|
||||
@@ -20,32 +20,32 @@ import { getItem } from '@app/common/local-storage';
|
||||
import { updateProvider } from '../provider/actions';
|
||||
import { COMMENTS_PATCH } from '../comments/types';
|
||||
|
||||
export const fetchUser = (): StoreAction<Promise<User | null>> => async dispatch => {
|
||||
const user = await api.getUser();
|
||||
dispatch({
|
||||
function setUser(user: User | null = null) {
|
||||
return {
|
||||
type: USER_SET,
|
||||
user,
|
||||
});
|
||||
} as USER_SET_ACTION;
|
||||
}
|
||||
|
||||
export const fetchUser = (): StoreAction<Promise<User | null>> => async dispatch => {
|
||||
const user = await api.getUser();
|
||||
dispatch(setUser(user));
|
||||
return user;
|
||||
};
|
||||
|
||||
export const logIn = (provider: AuthProvider): StoreAction<Promise<User | null>> => async dispatch => {
|
||||
const user = await api.logIn(provider);
|
||||
|
||||
dispatch(updateProvider({ name: provider.name }));
|
||||
dispatch({
|
||||
type: USER_SET,
|
||||
user,
|
||||
});
|
||||
dispatch(setUser(user));
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
export const logout = (): StoreAction<Promise<void>> => async dispatch => {
|
||||
await api.logOut();
|
||||
dispatch(unsetCommentMode());
|
||||
dispatch({
|
||||
type: USER_SET,
|
||||
user: null,
|
||||
});
|
||||
dispatch(setUser());
|
||||
};
|
||||
|
||||
export const fetchBlockedUsers = (): StoreAction<Promise<BlockedUser[]>> => async dispatch => {
|
||||
@@ -134,9 +134,9 @@ export const setVerifiedStatus = (id: User['id'], status: boolean): StoreAction<
|
||||
getState
|
||||
) => {
|
||||
if (status) {
|
||||
await api.setVerifyStatus(id);
|
||||
await api.setVerifiedStatus(id);
|
||||
} else {
|
||||
await api.removeVerifyStatus(id);
|
||||
await api.removeVerifiedStatus(id);
|
||||
}
|
||||
const comments = Object.values(getState().comments).filter(c => c.user.id === id);
|
||||
if (!comments.length) return;
|
||||
@@ -149,14 +149,6 @@ export const setVerifiedStatus = (id: User['id'], status: boolean): StoreAction<
|
||||
});
|
||||
};
|
||||
|
||||
export const setSettingsVisibility = (state: boolean): StoreAction<boolean> => dispatch => {
|
||||
dispatch({
|
||||
type: SETTINGS_VISIBLE_SET,
|
||||
state,
|
||||
});
|
||||
return state;
|
||||
};
|
||||
|
||||
export const setUserSubscribed = (isSubscribed: boolean) => ({
|
||||
type: USER_SUBSCRIPTION_SET,
|
||||
payload: isSubscribed,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import api from '@app/common/api';
|
||||
import * as api from '@app/common/api';
|
||||
import { User } from '@app/common/types';
|
||||
|
||||
import { fetchUser, logIn, logout } from './actions';
|
||||
|
||||
@@ -5,8 +5,6 @@ import {
|
||||
USER_BAN,
|
||||
USER_UNBAN,
|
||||
USER_ACTIONS,
|
||||
SETTINGS_VISIBLE_SET_ACTION,
|
||||
SETTINGS_VISIBLE_SET,
|
||||
USER_BANLIST_SET,
|
||||
USER_HIDELIST_SET,
|
||||
USER_HIDE,
|
||||
@@ -76,14 +74,4 @@ export const hiddenUsers = (state: { [id: string]: User } = {}, action: USER_ACT
|
||||
}
|
||||
};
|
||||
|
||||
export const isSettingsVisible = (state: boolean = false, action: SETTINGS_VISIBLE_SET_ACTION): boolean => {
|
||||
switch (action.type) {
|
||||
case SETTINGS_VISIBLE_SET: {
|
||||
return action.state;
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default { user, bannedUsers, hiddenUsers, isSettingsVisible };
|
||||
export default { user, bannedUsers, hiddenUsers };
|
||||
|
||||
@@ -49,13 +49,6 @@ export interface USER_UNHIDE_ACTION {
|
||||
id: User['id'];
|
||||
}
|
||||
|
||||
export const SETTINGS_VISIBLE_SET = 'SETTINGS_VISIBLE/SET';
|
||||
|
||||
export interface SETTINGS_VISIBLE_SET_ACTION {
|
||||
type: typeof SETTINGS_VISIBLE_SET;
|
||||
state: boolean;
|
||||
}
|
||||
|
||||
export const USER_SUBSCRIPTION_SET = 'USER_SUBSCRIPTION/SET';
|
||||
|
||||
export interface USER_SUBSCRIPTION_SET_ACTION {
|
||||
@@ -71,5 +64,4 @@ export type USER_ACTIONS =
|
||||
| USER_HIDELIST_SET_ACTION
|
||||
| USER_HIDE_ACTION
|
||||
| USER_UNHIDE_ACTION
|
||||
| SETTINGS_VISIBLE_SET_ACTION
|
||||
| USER_SUBSCRIPTION_SET_ACTION;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import parseQuery from './parseQuery';
|
||||
|
||||
describe('parseQuery', () => {
|
||||
it('should return empty object', () => {
|
||||
expect(parseQuery('')).toEqual({});
|
||||
expect(parseQuery('?')).toEqual({});
|
||||
});
|
||||
|
||||
it('should add empty field to object', () => {
|
||||
expect(parseQuery('?a')).toEqual({ a: '' });
|
||||
});
|
||||
|
||||
it('should add empty field and field with param to object', () => {
|
||||
expect(parseQuery('?a&b=1')).toEqual({ a: '', b: '1' });
|
||||
});
|
||||
|
||||
it('should add all params to object', () => {
|
||||
expect(parseQuery('?a=1&b=1')).toEqual({ a: '1', b: '1' });
|
||||
});
|
||||
|
||||
it('should convert urlencoded param', () => {
|
||||
expect(parseQuery('?x=%D1%8B%D1%84%D0%B2%D0%B0%D1%84%D1%8B%D0%B2%D1%84%D1%8B')).toEqual({ x: 'ыфвафывфы' });
|
||||
});
|
||||
});
|
||||
@@ -1,20 +1,16 @@
|
||||
/** converts widnow.location.search into object */
|
||||
export function parseQuery(search: string): { [key: string]: string } {
|
||||
export default function parseQuery(search: string = window.location.search) {
|
||||
if (search.length < 2) return {};
|
||||
|
||||
return search
|
||||
.substr(1)
|
||||
.split('&')
|
||||
.map((chunk): [string, string] => {
|
||||
const parts = chunk.split('=');
|
||||
if (parts.length < 2) {
|
||||
parts[1] = '';
|
||||
} else {
|
||||
parts[1] = decodeURIComponent(parts[1]);
|
||||
}
|
||||
return parts as [string, string];
|
||||
})
|
||||
.reduce<{ [key: string]: string }>((c, x) => {
|
||||
c[x[0]] = x[1];
|
||||
return c;
|
||||
}, {});
|
||||
.reduce((accum, param) => {
|
||||
const [key, value] = param.split('=');
|
||||
|
||||
return {
|
||||
...accum,
|
||||
[key]: value ? decodeURIComponent(value) : '',
|
||||
};
|
||||
}, {} as Record<string, string>);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ export type Message =
|
||||
user: User;
|
||||
}
|
||||
| { isUserInfoShown: false }
|
||||
| { scrollTo: number };
|
||||
| { scrollTo: number }
|
||||
| { remarkIframeHeight: number };
|
||||
|
||||
/**
|
||||
* Sends message to parent window
|
||||
|
||||
+10
-7
@@ -1,6 +1,7 @@
|
||||
<html>
|
||||
<title>Privacy Policy</title>
|
||||
|
||||
<head>
|
||||
<title>Privacy Policy</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Privacy Policy</h1>
|
||||
<p>
|
||||
@@ -39,10 +40,12 @@
|
||||
<h3>Service Providers</h3>
|
||||
<p>
|
||||
We may employ third-party companies and individuals due to the following reasons:
|
||||
<li>To facilitate our Service</li>
|
||||
<li>To provide the Service on our behalf</li>
|
||||
<li>To perform Service-related services</li>
|
||||
<li>To assist us in analyzing how our Service is used</li>
|
||||
<ul>
|
||||
<li>To facilitate our Service</li>
|
||||
<li>To provide the Service on our behalf</li>
|
||||
<li>To perform Service-related services</li>
|
||||
<li>To assist us in analyzing how our Service is used</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@@ -85,4 +88,4 @@
|
||||
<p>If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user