1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 12:56:41 +07:00

added initial profile dialog

This commit is contained in:
Fedor Katurov 2019-11-11 16:01:21 +07:00
parent f6baedc4cd
commit 618c2e3275
28 changed files with 315 additions and 58 deletions

View file

@ -5,6 +5,7 @@ import { getURL } from '~/utils/dom';
import { Icon } from '~/components/input/Icon'; import { Icon } from '~/components/input/Icon';
import { IUser } from '~/redux/auth/types'; import { IUser } from '~/redux/auth/types';
import { PRESETS } from '~/constants/urls'; import { PRESETS } from '~/constants/urls';
import { Link } from 'react-router-dom';
interface IProps { interface IProps {
user: Partial<IUser>; user: Partial<IUser>;
@ -14,7 +15,8 @@ interface IProps {
const UserButton: FC<IProps> = ({ user: { username, photo }, onLogout }) => ( const UserButton: FC<IProps> = ({ user: { username, photo }, onLogout }) => (
<div className={styles.wrap}> <div className={styles.wrap}>
<Group horizontal className={styles.user_button}> <Group horizontal className={styles.user_button}>
<div>{username}</div> <Link to={`/~${username}`}>{username}</Link>
<div <div
className={styles.user_avatar} className={styles.user_avatar}
style={{ backgroundImage: `url('${getURL(photo, PRESETS.avatar)}')` }} style={{ backgroundImage: `url('${getURL(photo, PRESETS.avatar)}')` }}

View file

@ -44,7 +44,7 @@
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
color: #cccccc; color: #cccccc;
word-break: break-all; word-break: break-word;
b { b {
font-weight: 600; font-weight: 600;

View file

@ -6,21 +6,21 @@ import { Group } from '~/components/containers/Group';
const ParagraphPlaceholder = ({}) => ( const ParagraphPlaceholder = ({}) => (
<Group> <Group>
<div className={styles.para}> <div className={styles.para}>
<Placeholder width={120} /> <Placeholder width="120px" />
<Placeholder width={60} /> <Placeholder width="60px" />
<Placeholder width={30} /> <Placeholder width="30px" />
<Placeholder width={70} /> <Placeholder width="70px" />
<Placeholder width={160} /> <Placeholder width="160px" />
<Placeholder width={30} /> <Placeholder width="30px" />
</div> </div>
<div className={styles.para}> <div className={styles.para}>
<Placeholder width={40} /> <Placeholder width="40px" />
<Placeholder width={30} /> <Placeholder width="30px" />
<Placeholder width={120} /> <Placeholder width="120px" />
<Placeholder width={70} /> <Placeholder width="70px" />
<Placeholder width={160} /> <Placeholder width="160px" />
<Placeholder width={30} /> <Placeholder width="30px" />
</div> </div>
</Group> </Group>
); );

View file

@ -2,16 +2,13 @@ import React, { FC } from 'react';
import * as styles from './styles.scss'; import * as styles from './styles.scss';
interface IProps { interface IProps {
width?: number; width?: string;
height?: number; height?: number;
color?: string; color?: string;
} }
const Placeholder: FC<IProps> = ({ width = 120, height, color }) => ( const Placeholder: FC<IProps> = ({ width = '120px', height, color }) => (
<div <div className={styles.placeholder} style={{ height, color, width }} />
className={styles.placeholder}
style={{ height, color, width }}
/>
); );
export { Placeholder }; export { Placeholder };

View file

@ -5,8 +5,9 @@ export const API = {
USER: { USER: {
LOGIN: '/auth/login', LOGIN: '/auth/login',
VKONTAKTE_LOGIN: `${process.env.API_HOST}/auth/vkontakte`, VKONTAKTE_LOGIN: `${process.env.API_HOST}/auth/vkontakte`,
ME: '/auth/', // ME: '/auth/',
UPLOAD: (target, type) => `/upload/${target}/${type}`, UPLOAD: (target, type) => `/upload/${target}/${type}`,
PROFILE: (username: string) => `/user/${username}`,
}, },
NODE: { NODE: {
SAVE: '/node/', SAVE: '/node/',

View file

@ -13,6 +13,7 @@ export const URLS = {
BACKEND_DOWN: '/oopsie', BACKEND_DOWN: '/oopsie',
}, },
NODE_URL: (id: number | string) => `/post${id}`, NODE_URL: (id: number | string) => `/post${id}`,
PROFILE: (username: string) => `/~${username}`,
}; };
export const PRESETS = { export const PRESETS = {

View file

@ -1,4 +1,4 @@
import React, { FC, useEffect } from 'react'; import React, { FC } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { ConnectedRouter } from 'connected-react-router'; import { ConnectedRouter } from 'connected-react-router';
@ -8,7 +8,6 @@ import { FlowLayout } from '~/containers/flow/FlowLayout';
import { MainLayout } from '~/containers/main/MainLayout'; import { MainLayout } from '~/containers/main/MainLayout';
import { ImageExample } from '~/containers/examples/ImageExample'; import { ImageExample } from '~/containers/examples/ImageExample';
import { EditorExample } from '~/containers/examples/EditorExample'; import { EditorExample } from '~/containers/examples/EditorExample';
import { HorizontalExample } from '~/containers/examples/HorizontalExample';
import { Sprites } from '~/sprites/Sprites'; import { Sprites } from '~/sprites/Sprites';
import { URLS } from '~/constants/urls'; import { URLS } from '~/constants/urls';
import { Modal } from '~/containers/dialogs/Modal'; import { Modal } from '~/containers/dialogs/Modal';
@ -19,6 +18,7 @@ import { NodeLayout } from './node/NodeLayout';
import { BottomContainer } from '~/containers/main/BottomContainer'; import { BottomContainer } from '~/containers/main/BottomContainer';
import { BorisLayout } from './node/BorisLayout'; import { BorisLayout } from './node/BorisLayout';
import { ErrorNotFound } from './pages/ErrorNotFound'; import { ErrorNotFound } from './pages/ErrorNotFound';
import { ProfileLayout } from './profile/ProfileLayout';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
modal: selectModal(state), modal: selectModal(state),

View file

@ -0,0 +1,28 @@
import React, { FC } from 'react';
import { BetterScrollDialog } from '../BetterScrollDialog';
import styles from './styles.scss';
import { ProfileInfo } from '~/containers/profile/ProfileInfo';
import { IDialogProps } from '~/redux/types';
import { connect } from 'react-redux';
import { selectAuthProfile } from '~/redux/auth/selectors';
const mapStateToProps = selectAuthProfile;
const mapDispatchToProps = {};
type IProps = IDialogProps & ReturnType<typeof mapStateToProps> & {};
const ProfileDialogUnconnected: FC<IProps> = ({ onRequestClose, is_loading, user }) => (
<BetterScrollDialog
header={<ProfileInfo is_loading={is_loading} user={user} />}
onClose={onRequestClose}
>
<div className={styles.example} />
</BetterScrollDialog>
);
const ProfileDialog = connect(
mapStateToProps,
mapDispatchToProps
)(ProfileDialogUnconnected);
export { ProfileDialog };

View file

@ -9,6 +9,7 @@ import styles from './styles.scss';
import { CommentForm } from '~/components/node/CommentForm'; import { CommentForm } from '~/components/node/CommentForm';
import { Group } from '~/components/containers/Group'; import { Group } from '~/components/containers/Group';
import boris from '~/sprites/boris_robot.svg'; import boris from '~/sprites/boris_robot.svg';
import { NodeNoComments } from '~/components/node/NodeNoComments';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
node: selectNode(state), node: selectNode(state),
@ -78,7 +79,11 @@ const BorisLayoutUnconnected: FC<IProps> = ({
<Group className={styles.content}> <Group className={styles.content}>
{is_user && <CommentForm id={0} />} {is_user && <CommentForm id={0} />}
{is_loading_comments ? (
<NodeNoComments is_loading />
) : (
<NodeComments comments={comments} /> <NodeComments comments={comments} />
)}
</Group> </Group>
</div> </div>
</div> </div>

View file

@ -11,6 +11,7 @@
padding: $gap; padding: $gap;
background: $content_bg; background: $content_bg;
border-radius: $radius; border-radius: $radius;
flex: 0 1 $limited_width;
} }
.column { .column {
@ -70,12 +71,13 @@
align-items: flex-start; align-items: flex-start;
justify-content: center; justify-content: center;
flex-direction: row; flex-direction: row;
width: 80%; flex: 0 1 $limited_width;
margin: auto;
@include tablet {
width: 100%; width: 100%;
} // margin: auto;
// @include tablet {
// width: 100%;
// }
} }
.image { .image {
@ -101,9 +103,10 @@
.caption { .caption {
position: absolute; position: absolute;
left: 0; left: 50%;
bottom: 0; bottom: 0;
width: 50%; width: 100%;
max-width: $limited_width;
height: 100%; height: 100%;
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
@ -114,6 +117,7 @@
flex-direction: column; flex-direction: column;
padding-bottom: $gap * 2; padding-bottom: $gap * 2;
padding: 0 10% $gap * 2; padding: 0 10% $gap * 2;
transform: translate(-50%, 0);
@include tablet { @include tablet {
align-items: flex-start; align-items: flex-start;

View file

@ -0,0 +1,27 @@
import React, { FC } from 'react';
import { IUser } from '~/redux/auth/types';
import styles from './styles.scss';
import { Grid } from '~/components/containers/Grid';
import { Group } from '~/components/containers/Group';
import { Placeholder } from '~/components/placeholders/Placeholder';
interface IProps {
user?: IUser;
is_loading?: boolean;
}
const ProfileInfo: FC<IProps> = ({ user, is_loading = false }) => (
<Group className={styles.wrap} horizontal>
<div className={styles.avatar} />
<Group className={styles.field}>
<div className={styles.name}>{is_loading ? <Placeholder width="80%" /> : 'User Name'}</div>
<div className={styles.desription}>
{is_loading ? <Placeholder /> : 'Some description here'}
</div>
</Group>
</Group>
);
export { ProfileInfo };

View file

@ -0,0 +1,28 @@
.wrap {
justify-content: flex-start;
align-items: flex-start !important;
// min-height: 64px;
padding: $gap;
box-sizing: border-box;
}
.avatar {
@include outer_shadow();
border-radius: $radius;
width: 140px;
height: 140px;
background: $content_bg;
position: absolute;
top: -60px;
left: $gap;
}
.field {
padding-left: 140px;
flex: 1;
}
.name {
font: $font_24_bold;
}

View file

@ -0,0 +1,58 @@
import React, { FC, useEffect, useState } from 'react';
import { useRouteMatch, withRouter, RouteComponentProps } from 'react-router';
import styles from './styles.scss';
import { NodeNoComments } from '~/components/node/NodeNoComments';
import { Grid } from '~/components/containers/Grid';
import { CommentForm } from '~/components/node/CommentForm';
import { ProfileInfo } from '../ProfileInfo';
import * as NODE_ACTIONS from '~/redux/node/actions';
import { connect } from 'react-redux';
import { IUser } from '~/redux/auth/types';
import { Group } from '~/components/containers/Group';
const mapStateToProps = () => ({});
const mapDispatchToProps = {
nodeSetCoverImage: NODE_ACTIONS.nodeSetCoverImage,
};
type IProps = RouteComponentProps & typeof mapDispatchToProps & {};
const ProfileLayoutUnconnected: FC<IProps> = ({ history, nodeSetCoverImage }) => {
const {
params: { username },
} = useRouteMatch<{ username: string }>();
const [user, setUser] = useState<IUser>(null);
useEffect(() => {
if (user) setUser(null);
}, [username]);
useEffect(() => {
if (user && user.id && user.cover) {
nodeSetCoverImage(user.cover);
return () => nodeSetCoverImage(null);
}
}, [user]);
return (
<Group className={styles.wrap} horizontal>
<div className={styles.column}>
<ProfileInfo user={user} />
</div>
<Grid className={styles.content}>
<div className={styles.comments}>
<CommentForm id={0} />
<NodeNoComments is_loading={false} />
</div>
</Grid>
</Group>
);
};
const ProfileLayout = connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(ProfileLayoutUnconnected));
export { ProfileLayout };

View file

@ -0,0 +1,27 @@
.wrap {
display: flex;
align-items: flex-start !important;
justify-content: flex-start !important;
flex-direction: row;
flex: 1;
}
.content {
flex: 3;
// flex: 0 1 $limited_width;
// padding: $gap;
box-sizing: border-box;
}
.column {
flex: 1;
}
.info {
}
.comments {
background: $node_bg;
border-radius: $radius;
padding: $gap;
}

View file

@ -19,8 +19,8 @@ export const authSetToken = (token: IAuthState['token']) => ({
token, token,
}); });
export const gotPostMessage = ({ token }: { token: string }) => ({ export const gotAuthPostMessage = ({ token }: { token: string }) => ({
type: AUTH_USER_ACTIONS.GOT_POST_MESSAGE, type: AUTH_USER_ACTIONS.GOT_AUTH_POST_MESSAGE,
token, token,
}); });
@ -32,3 +32,13 @@ export const authSetUser = (profile: Partial<IUser>) => ({
export const authLogout = () => ({ export const authLogout = () => ({
type: AUTH_USER_ACTIONS.LOGOUT, type: AUTH_USER_ACTIONS.LOGOUT,
}); });
export const authOpenProfile = (username: string) => ({
type: AUTH_USER_ACTIONS.OPEN_PROFILE,
username,
});
export const authSetProfile = (profile: Partial<IAuthState['profile']>) => ({
type: AUTH_USER_ACTIONS.SET_PROFILE,
profile,
});

View file

@ -22,3 +22,12 @@ export const apiAuthGetUser = ({ access }): Promise<IResultWithStatus<{ user: IU
.get(API.USER.ME, configWithToken(access)) .get(API.USER.ME, configWithToken(access))
.then(resultMiddleware) .then(resultMiddleware)
.catch(errorMiddleware); .catch(errorMiddleware);
export const apiAuthGetUserProfile = ({
access,
username,
}): Promise<IResultWithStatus<{ user: IUser }>> =>
api
.get(API.USER.PROFILE(username), configWithToken(access))
.then(resultMiddleware)
.catch(errorMiddleware);

View file

@ -8,7 +8,9 @@ export const AUTH_USER_ACTIONS = {
LOGOUT: 'LOGOUT', LOGOUT: 'LOGOUT',
GOT_POST_MESSAGE: 'GOT_POST_MESSAGE', GOT_AUTH_POST_MESSAGE: 'GOT_POST_MESSAGE',
OPEN_PROFILE: 'OPEN_PROFILE',
SET_PROFILE: 'SET_PROFILE',
}; };
export const USER_ERRORS = { export const USER_ERRORS = {

View file

@ -14,23 +14,32 @@ const setLoginError: ActionHandler<typeof ActionCreators.userSetLoginError> = (
login: { login: {
...state.login, ...state.login,
error, error,
} },
}); });
const setUser: ActionHandler<typeof ActionCreators.authSetUser> = (state, { profile }) => ({ const setUser: ActionHandler<typeof ActionCreators.authSetUser> = (state, { profile }) => ({
...state, ...state,
user: { user: {
...state.user, ...state.user,
...profile ...profile,
} },
}); });
const setToken: ActionHandler<typeof ActionCreators.authSetToken> = (state, { token }) => ({ const setToken: ActionHandler<typeof ActionCreators.authSetToken> = (state, { token }) => ({
...state, token, ...state,
token,
}); });
const setProfile: ActionHandler<typeof ActionCreators.authSetProfile> = (state, { profile }) => ({
...state,
profile: {
...state.profile,
...profile,
},
});
export const AUTH_USER_HANDLERS = { export const AUTH_USER_HANDLERS = {
[AUTH_USER_ACTIONS.SET_LOGIN_ERROR]: setLoginError, [AUTH_USER_ACTIONS.SET_LOGIN_ERROR]: setLoginError,
[AUTH_USER_ACTIONS.SET_USER]: setUser, [AUTH_USER_ACTIONS.SET_USER]: setUser,
[AUTH_USER_ACTIONS.SET_TOKEN]: setToken, [AUTH_USER_ACTIONS.SET_TOKEN]: setToken,
[AUTH_USER_ACTIONS.SET_PROFILE]: setProfile,
}; };

View file

@ -10,10 +10,16 @@ const HANDLERS = {
const INITIAL_STATE: IAuthState = { const INITIAL_STATE: IAuthState = {
token: null, token: null,
user: { ...EMPTY_USER }, user: { ...EMPTY_USER },
login: { login: {
error: null, error: null,
is_loading: false, is_loading: false,
}, },
profile: {
is_loading: true,
user: null,
},
}; };
export default createReducer(INITIAL_STATE, HANDLERS); export default createReducer(INITIAL_STATE, HANDLERS);

View file

@ -1,19 +1,20 @@
import { call, put, takeLatest, select } from 'redux-saga/effects'; import { call, put, takeLatest, select, delay } from 'redux-saga/effects';
import { AUTH_USER_ACTIONS, EMPTY_USER, USER_ERRORS } from '~/redux/auth/constants'; import { AUTH_USER_ACTIONS, EMPTY_USER, USER_ERRORS } from '~/redux/auth/constants';
import { import {
authSetToken, authSetToken,
userSetLoginError, userSetLoginError,
authSetUser, authSetUser,
userSendLoginRequest, userSendLoginRequest,
gotPostMessage, gotAuthPostMessage,
authOpenProfile,
authSetProfile,
} from '~/redux/auth/actions'; } from '~/redux/auth/actions';
import { apiUserLogin, apiAuthGetUser } from '~/redux/auth/api'; import { apiUserLogin, apiAuthGetUser, apiAuthGetUserProfile } from '~/redux/auth/api';
import { modalSetShown } from '~/redux/modal/actions'; import { modalSetShown, modalShowDialog } from '~/redux/modal/actions';
import { selectToken } from './selectors'; import { selectToken } from './selectors';
import { IResultWithStatus } from '../types'; import { IResultWithStatus } from '../types';
import { IUser } from './types'; import { IUser } from './types';
import { REHYDRATE, RehydrateAction } from 'redux-persist'; import { REHYDRATE, RehydrateAction } from 'redux-persist';
import path from 'ramda/es/path';
import { selectModal } from '../modal/selectors'; import { selectModal } from '../modal/selectors';
import { IModalState } from '../modal/reducer'; import { IModalState } from '../modal/reducer';
import { DIALOGS } from '../modal/constants'; import { DIALOGS } from '../modal/constants';
@ -76,7 +77,7 @@ function* checkUserSaga({ key }: RehydrateAction) {
yield call(refreshUser); yield call(refreshUser);
} }
function* gotPostMessageSaga({ token }: ReturnType<typeof gotPostMessage>) { function* gotPostMessageSaga({ token }: ReturnType<typeof gotAuthPostMessage>) {
yield put(authSetToken(token)); yield put(authSetToken(token));
yield call(refreshUser); yield call(refreshUser);
@ -90,11 +91,29 @@ function* logoutSaga() {
yield put(authSetUser({ ...EMPTY_USER })); yield put(authSetUser({ ...EMPTY_USER }));
} }
function* openProfile({ username }: ReturnType<typeof authOpenProfile>) {
yield put(modalShowDialog(DIALOGS.PROFILE));
yield put(authSetProfile({ is_loading: true }));
const {
error,
data: { user },
} = yield call(reqWrapper, apiAuthGetUserProfile, { username });
if (error || !user) {
return yield put(modalSetShown(false));
}
yield put(authSetProfile({ is_loading: false }));
console.log({ username, user });
}
function* authSaga() { function* authSaga() {
yield takeLatest(REHYDRATE, checkUserSaga); yield takeLatest(REHYDRATE, checkUserSaga);
yield takeLatest(AUTH_USER_ACTIONS.LOGOUT, logoutSaga); yield takeLatest(AUTH_USER_ACTIONS.LOGOUT, logoutSaga);
yield takeLatest(AUTH_USER_ACTIONS.SEND_LOGIN_REQUEST, sendLoginRequestSaga); yield takeLatest(AUTH_USER_ACTIONS.SEND_LOGIN_REQUEST, sendLoginRequestSaga);
yield takeLatest(AUTH_USER_ACTIONS.GOT_POST_MESSAGE, gotPostMessageSaga); yield takeLatest(AUTH_USER_ACTIONS.GOT_AUTH_POST_MESSAGE, gotPostMessageSaga);
yield takeLatest(AUTH_USER_ACTIONS.OPEN_PROFILE, openProfile);
} }
export default authSaga; export default authSaga;

View file

@ -4,3 +4,4 @@ export const selectAuth = (state: IState): IState['auth'] => state.auth;
export const selectUser = (state: IState): IState['auth']['user'] => state.auth.user; export const selectUser = (state: IState): IState['auth']['user'] => state.auth.user;
export const selectToken = (state: IState): IState['auth']['token'] => state.auth.token; export const selectToken = (state: IState): IState['auth']['token'] => state.auth.token;
export const selectAuthLogin = (state: IState): IState['auth']['login'] => state.auth.login; export const selectAuthLogin = (state: IState): IState['auth']['login'] => state.auth.login;
export const selectAuthProfile = (state: IState): IState['auth']['profile'] => state.auth.profile;

View file

@ -26,4 +26,9 @@ export type IAuthState = Readonly<{
error: string; error: string;
is_loading: boolean; is_loading: boolean;
}; };
profile: {
is_loading: boolean;
user: IUser;
};
}>; }>;

View file

@ -7,6 +7,7 @@ import { EditorDialogVideo } from '~/containers/editors/EditorDialogVideo';
import { EditorDialogAudio } from '~/containers/editors/EditorDialogAudio'; import { EditorDialogAudio } from '~/containers/editors/EditorDialogAudio';
import { NODE_TYPES } from '../node/constants'; import { NODE_TYPES } from '../node/constants';
import { TestDialog } from '~/containers/dialogs/TestDialog'; import { TestDialog } from '~/containers/dialogs/TestDialog';
import { ProfileDialog } from '~/containers/dialogs/ProfileDialog';
export const MODAL_ACTIONS = { export const MODAL_ACTIONS = {
SET_SHOWN: 'MODAL.SET_SHOWN', SET_SHOWN: 'MODAL.SET_SHOWN',
@ -21,6 +22,7 @@ export const DIALOGS = {
EDITOR_AUDIO: 'EDITOR_AUDIO', EDITOR_AUDIO: 'EDITOR_AUDIO',
LOGIN: 'LOGIN', LOGIN: 'LOGIN',
LOADING: 'LOADING', LOADING: 'LOADING',
PROFILE: 'PROFILE',
TEST: 'TEST', TEST: 'TEST',
}; };
@ -32,6 +34,7 @@ export const DIALOG_CONTENT = {
[DIALOGS.LOGIN]: LoginDialog, [DIALOGS.LOGIN]: LoginDialog,
[DIALOGS.LOADING]: LoadingDialog, [DIALOGS.LOADING]: LoadingDialog,
[DIALOGS.TEST]: TestDialog, [DIALOGS.TEST]: TestDialog,
[DIALOGS.PROFILE]: ProfileDialog,
}; };
export const NODE_EDITOR_DIALOGS = { export const NODE_EDITOR_DIALOGS = {

View file

@ -25,7 +25,7 @@ import playerSaga from '~/redux/player/sagas';
import { IAuthState } from '~/redux/auth/types'; import { IAuthState } from '~/redux/auth/types';
import modalReducer, { IModalState } from '~/redux/modal/reducer'; import modalReducer, { IModalState } from '~/redux/modal/reducer';
import { gotPostMessage } from './auth/actions'; import { gotAuthPostMessage, authOpenProfile } from './auth/actions';
const authPersistConfig: PersistConfig = { const authPersistConfig: PersistConfig = {
key: 'auth', key: 'auth',
@ -46,6 +46,13 @@ export interface IState {
export const sagaMiddleware = createSagaMiddleware(); export const sagaMiddleware = createSagaMiddleware();
export const history = createBrowserHistory(); export const history = createBrowserHistory();
history.listen(({ pathname }) => {
if (pathname.match(/~([\wа-яА-Я]+)/)) {
const [, username] = pathname.match(/~([\wа-яА-Я]+)/);
window.postMessage({ type: 'username', username }, '*');
}
});
const composeEnhancers = const composeEnhancers =
typeof window === 'object' && (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ typeof window === 'object' && (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) ? (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
@ -72,9 +79,11 @@ export function configureStore(): { store: Store<IState>; persistor: Persistor }
sagaMiddleware.run(playerSaga); sagaMiddleware.run(playerSaga);
window.addEventListener('message', message => { window.addEventListener('message', message => {
if (!message || !message.data || message.data.type !== 'oauth_login' || !message.data.token) if (message && message.data && message.data.type === 'oauth_login' && message.data.token)
return; return store.dispatch(gotAuthPostMessage({ token: message.data.token }));
store.dispatch(gotPostMessage({ token: message.data.token }));
if (message && message.data && message.data.type === 'username' && message.data.username)
return store.dispatch(authOpenProfile(message.data.username));
}); });
const persistor = persistStore(store); const persistor = persistStore(store);

View file

@ -87,6 +87,15 @@ body {
font: $font_24_bold; font: $font_24_bold;
} }
:global(.username) {
background: transparentize($color: #000000, $amount: 0.8);
padding: 2px 4px;
border-radius: 4px;
cursor: pointer;
color: $wisegreen;
font-weight: bold;
}
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 18px; width: 18px;
height: 18px; height: 18px;
@ -117,14 +126,6 @@ body {
border-radius: 50px; border-radius: 50px;
} }
// ::-webkit-scrollbar-track:hover {
// background: #666666;
// }
//
// ::-webkit-scrollbar-track:active {
// background: #333333;
// }
::-webkit-scrollbar-corner { ::-webkit-scrollbar-corner {
background: transparent; background: transparent;
} }

View file

@ -16,6 +16,7 @@ $dialog_radius: $radius * 2;
$input_height: 36px; $input_height: 36px;
$info_height: 24px; $info_height: 24px;
$limited_width: 940px;
$panel_size: 64px; $panel_size: 64px;
$node_title_height: $panel_size; $node_title_height: $panel_size;

View file

@ -79,6 +79,10 @@ export const formatText = (text: string): string =>
.replace(/(\n{2,})/gi, '\n') .replace(/(\n{2,})/gi, '\n')
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
.replace(/>/g, '&gt;') .replace(/>/g, '&gt;')
.replace(
/~([\wа-яА-Я\-]+)/giu,
'<span class="username" onClick="window.postMessage({ type: \'username\', username: \'$1\'});">~$1</span>'
)
.replace(/:\/\//gim, ':|--|') .replace(/:\/\//gim, ':|--|')
.replace(/(\/\/[^\n]+)/gim, '<span class="grey">$1</span>') .replace(/(\/\/[^\n]+)/gim, '<span class="grey">$1</span>')
.replace(/(\/\*[\s\S]*?\*\/)/gim, '<span class="grey">$1</span>') .replace(/(\/\*[\s\S]*?\*\/)/gim, '<span class="grey">$1</span>')