Add main chat toggle

This commit is contained in:
Berkeley Martinez
2016-06-03 23:34:28 -07:00
parent 253fb52c50
commit 1acd3139c3
9 changed files with 117 additions and 14 deletions

View File

@ -0,0 +1,61 @@
import { Observable } from 'rx';
import Chat from 'gitter-sidecar';
import types from '../../common/app/redux/types';
function createHeader(document) {
const div = document.createElement('div');
const span = document.createElement('span');
const actionBar = document.querySelector(
'#chat-embed-main > .gitter-chat-embed-action-bar'
);
span.appendChild(document.createTextNode('Free Code Camp\'s Main Chat'));
div.className = 'chat-embed-main-title';
div.appendChild(span);
actionBar.insertBefore(div, actionBar.firstChild);
}
export default function gitterSaga(actions$, getState, { document }) {
let mainChatTitleAdded = false;
const mainChatContainer = document.createElement('aside');
mainChatContainer.id = 'chat-embed-main';
mainChatContainer.className = 'gitter-chat-embed is-collapsed';
document.body.appendChild(mainChatContainer);
const mainChat = new Chat({
room: 'freecodecamp/freecodecamp',
activationElement: false,
targetElement: mainChatContainer
});
const mainChatToggle$ = Observable.fromEventPattern(
h => mainChatContainer.addEventListener('gitter-chat-toggle', h),
h => mainChatContainer.removeEventListener('gitter-chat-toggle', h)
)
.map(e => {
const { isMainChatOpen } = getState().app;
if (!mainChatTitleAdded) {
mainChatTitleAdded = true;
createHeader(document);
}
if (isMainChatOpen === e.detail.state) {
return null;
}
return { type: types.toggleMainChat };
});
return Observable.merge(
mainChatToggle$,
actions$
.filter(({ type }) => (
type === types.openMainChat ||
type === types.closeMainChat ||
type === types.toggleMainChat
))
.map(() => {
const { isMainChatOpen } = getState().app;
mainChat.toggleChat(isMainChatOpen);
if (!isMainChatOpen) {
document.activeElement.blur();
}
return null;
})
);
}

View File

@ -6,6 +6,7 @@ import windowSaga from './window-saga';
import executeChallengeSaga from './execute-challenge-saga'; import executeChallengeSaga from './execute-challenge-saga';
import frameSaga from './frame-saga'; import frameSaga from './frame-saga';
import codeStorageSaga from './code-storage-saga'; import codeStorageSaga from './code-storage-saga';
import gitterSaga from './gitter-saga';
export default [ export default [
errSaga, errSaga,
@ -15,5 +16,6 @@ export default [
windowSaga, windowSaga,
executeChallengeSaga, executeChallengeSaga,
frameSaga, frameSaga,
codeStorageSaga codeStorageSaga,
gitterSaga
]; ];

View File

@ -11,7 +11,8 @@ import {
fetchUser, fetchUser,
initWindowHeight, initWindowHeight,
updateNavHeight, updateNavHeight,
toggleMapDrawer toggleMapDrawer,
toggleMainChat
} from './redux/actions'; } from './redux/actions';
import { submitChallenge } from './routes/challenges/redux/actions'; import { submitChallenge } from './routes/challenges/redux/actions';
@ -53,7 +54,8 @@ const bindableActions = {
updateNavHeight, updateNavHeight,
fetchUser, fetchUser,
submitChallenge, submitChallenge,
toggleMapDrawer toggleMapDrawer,
toggleMainChat
}; };
const fetchContainerOptions = { const fetchContainerOptions = {
@ -79,7 +81,8 @@ export class FreeCodeCamp extends React.Component {
submitChallenge: PropTypes.func, submitChallenge: PropTypes.func,
isMapDrawerOpen: PropTypes.bool, isMapDrawerOpen: PropTypes.bool,
isMapAlreadyLoaded: PropTypes.bool, isMapAlreadyLoaded: PropTypes.bool,
toggleMapDrawer: PropTypes.func toggleMapDrawer: PropTypes.func,
toggleMainChat: PropTypes.func
}; };
componentWillReceiveProps({ componentWillReceiveProps({
@ -141,14 +144,16 @@ export class FreeCodeCamp extends React.Component {
updateNavHeight, updateNavHeight,
isMapDrawerOpen, isMapDrawerOpen,
isMapAlreadyLoaded, isMapAlreadyLoaded,
toggleMapDrawer toggleMapDrawer,
toggleMainChat
} = this.props; } = this.props;
const navProps = { const navProps = {
username, username,
points, points,
picture, picture,
updateNavHeight, updateNavHeight,
toggleMapDrawer toggleMapDrawer,
toggleMainChat
}; };
return ( return (

View File

@ -40,7 +40,8 @@ export default class extends React.Component {
signedIn: PropTypes.bool, signedIn: PropTypes.bool,
username: PropTypes.string, username: PropTypes.string,
updateNavHeight: PropTypes.func, updateNavHeight: PropTypes.func,
toggleMapDrawer: PropTypes.func toggleMapDrawer: PropTypes.func,
toggleMainChat: PropTypes.func
}; };
componentDidMount() { componentDidMount() {
@ -75,6 +76,19 @@ export default class extends React.Component {
); );
} }
renderChat(toggleMainChat) {
return (
<NavItem
eventKey={ 2 }
href='//gitter.im/freecodecamp/freecodecamp'
onClick={ toggleMainChat }
target='_blank'
>
Chat
</NavItem>
);
}
renderLinks() { renderLinks() {
return navLinks.map(({ content, link, react, target }, index) => { return navLinks.map(({ content, link, react, target }, index) => {
if (react) { if (react) {
@ -144,7 +158,8 @@ export default class extends React.Component {
username, username,
points, points,
picture, picture,
toggleMapDrawer toggleMapDrawer,
toggleMainChat
} = this.props; } = this.props;
const { router } = this.context; const { router } = this.context;
const isOnMap = router.isActive('/map'); const isOnMap = router.isActive('/map');
@ -161,6 +176,7 @@ export default class extends React.Component {
navbar={ true } navbar={ true }
pullRight={ true }> pullRight={ true }>
{ this.renderMapLink(isOnMap, toggleMapDrawer) } { this.renderMapLink(isOnMap, toggleMapDrawer) }
{ this.renderChat(toggleMainChat) }
{ this.renderLinks() } { this.renderLinks() }
{ this.renderPoints(username, points) } { this.renderPoints(username, points) }
{ this.renderSignin(username, picture) } { this.renderSignin(username, picture) }

View File

@ -1,8 +1,4 @@
[{ [{
"content": "Chat",
"link": "//gitter.im/FreeCodeCamp/FreeCodeCamp",
"target": "_blank"
},{
"content": "Forum", "content": "Forum",
"link": "http://forum.freecodecamp.com/", "link": "http://forum.freecodecamp.com/",
"target": "_blank" "target": "_blank"

View File

@ -53,3 +53,14 @@ export const toggleMapDrawer = createAction(
types.toggleMapDrawer, types.toggleMapDrawer,
e => e.preventDefault() e => e.preventDefault()
); );
export const toggleWikiDrawer = createAction(types.toggleWikiDrawer);
// chat
export const toggleMainChat = createAction(
types.toggleMainChat,
e => {
if (!(e.ctrlKey || e.metaKey)) {
e.preventDefault();
}
}
);

View File

@ -9,7 +9,8 @@ const initialState = {
isSignedIn: false, isSignedIn: false,
csrfToken: '', csrfToken: '',
windowHeight: 0, windowHeight: 0,
navHeight: 0 navHeight: 0,
isMainChatOpen: false
}; };
export default handleActions( export default handleActions(
@ -46,6 +47,10 @@ export default handleActions(
...state, ...state,
isMapAlreadyLoaded: true, isMapAlreadyLoaded: true,
isMapDrawerOpen: !state.isMapDrawerOpen isMapDrawerOpen: !state.isMapDrawerOpen
}),
[types.toggleMainChat]: state => ({
...state,
isMainChatOpen: !state.isMainChatOpen
}) })
}, },
initialState initialState

View File

@ -22,5 +22,11 @@ export default createTypes([
'updateHikesData', 'updateHikesData',
// drawers // drawers
'toggleMapDrawer' 'toggleMapDrawer',
'toggleWikiDrawer',
// main chat
'openMainChat',
'closeMainChat',
'toggleMainChat'
], 'app'); ], 'app');

View File

@ -50,6 +50,7 @@
"express-validator": "^2.18.0", "express-validator": "^2.18.0",
"fetchr": "~0.5.12", "fetchr": "~0.5.12",
"frameguard": "^2.0.0", "frameguard": "^2.0.0",
"gitter-sidecar": "^1.2.3",
"helmet": "^2.0.0", "helmet": "^2.0.0",
"helmet-csp": "^1.0.3", "helmet-csp": "^1.0.3",
"history": "^2.0.0", "history": "^2.0.0",