From 1acd3139c33848cda09e5dbfb7ab6b408486ce2e Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Fri, 3 Jun 2016 23:34:28 -0700 Subject: [PATCH] Add main chat toggle --- client/sagas/gitter-saga.js | 61 ++++++++++++++++++++++++++++ client/sagas/index.js | 4 +- common/app/App.jsx | 15 ++++--- common/app/components/Nav/Nav.jsx | 20 ++++++++- common/app/components/Nav/links.json | 4 -- common/app/redux/actions.js | 11 +++++ common/app/redux/reducer.js | 7 +++- common/app/redux/types.js | 8 +++- package.json | 1 + 9 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 client/sagas/gitter-saga.js diff --git a/client/sagas/gitter-saga.js b/client/sagas/gitter-saga.js new file mode 100644 index 0000000000..158937fab2 --- /dev/null +++ b/client/sagas/gitter-saga.js @@ -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; + }) + ); +} diff --git a/client/sagas/index.js b/client/sagas/index.js index e1c78d0243..24a84a73e6 100644 --- a/client/sagas/index.js +++ b/client/sagas/index.js @@ -6,6 +6,7 @@ import windowSaga from './window-saga'; import executeChallengeSaga from './execute-challenge-saga'; import frameSaga from './frame-saga'; import codeStorageSaga from './code-storage-saga'; +import gitterSaga from './gitter-saga'; export default [ errSaga, @@ -15,5 +16,6 @@ export default [ windowSaga, executeChallengeSaga, frameSaga, - codeStorageSaga + codeStorageSaga, + gitterSaga ]; diff --git a/common/app/App.jsx b/common/app/App.jsx index ab67446dd3..a0839db4aa 100644 --- a/common/app/App.jsx +++ b/common/app/App.jsx @@ -11,7 +11,8 @@ import { fetchUser, initWindowHeight, updateNavHeight, - toggleMapDrawer + toggleMapDrawer, + toggleMainChat } from './redux/actions'; import { submitChallenge } from './routes/challenges/redux/actions'; @@ -53,7 +54,8 @@ const bindableActions = { updateNavHeight, fetchUser, submitChallenge, - toggleMapDrawer + toggleMapDrawer, + toggleMainChat }; const fetchContainerOptions = { @@ -79,7 +81,8 @@ export class FreeCodeCamp extends React.Component { submitChallenge: PropTypes.func, isMapDrawerOpen: PropTypes.bool, isMapAlreadyLoaded: PropTypes.bool, - toggleMapDrawer: PropTypes.func + toggleMapDrawer: PropTypes.func, + toggleMainChat: PropTypes.func }; componentWillReceiveProps({ @@ -141,14 +144,16 @@ export class FreeCodeCamp extends React.Component { updateNavHeight, isMapDrawerOpen, isMapAlreadyLoaded, - toggleMapDrawer + toggleMapDrawer, + toggleMainChat } = this.props; const navProps = { username, points, picture, updateNavHeight, - toggleMapDrawer + toggleMapDrawer, + toggleMainChat }; return ( diff --git a/common/app/components/Nav/Nav.jsx b/common/app/components/Nav/Nav.jsx index ab7e3bf2a6..514090b942 100644 --- a/common/app/components/Nav/Nav.jsx +++ b/common/app/components/Nav/Nav.jsx @@ -40,7 +40,8 @@ export default class extends React.Component { signedIn: PropTypes.bool, username: PropTypes.string, updateNavHeight: PropTypes.func, - toggleMapDrawer: PropTypes.func + toggleMapDrawer: PropTypes.func, + toggleMainChat: PropTypes.func }; componentDidMount() { @@ -75,6 +76,19 @@ export default class extends React.Component { ); } + renderChat(toggleMainChat) { + return ( + + Chat + + ); + } + renderLinks() { return navLinks.map(({ content, link, react, target }, index) => { if (react) { @@ -144,7 +158,8 @@ export default class extends React.Component { username, points, picture, - toggleMapDrawer + toggleMapDrawer, + toggleMainChat } = this.props; const { router } = this.context; const isOnMap = router.isActive('/map'); @@ -161,6 +176,7 @@ export default class extends React.Component { navbar={ true } pullRight={ true }> { this.renderMapLink(isOnMap, toggleMapDrawer) } + { this.renderChat(toggleMainChat) } { this.renderLinks() } { this.renderPoints(username, points) } { this.renderSignin(username, picture) } diff --git a/common/app/components/Nav/links.json b/common/app/components/Nav/links.json index 3abf097a6f..d3bf655b54 100644 --- a/common/app/components/Nav/links.json +++ b/common/app/components/Nav/links.json @@ -1,8 +1,4 @@ [{ - "content": "Chat", - "link": "//gitter.im/FreeCodeCamp/FreeCodeCamp", - "target": "_blank" -},{ "content": "Forum", "link": "http://forum.freecodecamp.com/", "target": "_blank" diff --git a/common/app/redux/actions.js b/common/app/redux/actions.js index 35d2445b0d..f1cd0a43b1 100644 --- a/common/app/redux/actions.js +++ b/common/app/redux/actions.js @@ -53,3 +53,14 @@ export const toggleMapDrawer = createAction( types.toggleMapDrawer, e => e.preventDefault() ); +export const toggleWikiDrawer = createAction(types.toggleWikiDrawer); + +// chat +export const toggleMainChat = createAction( + types.toggleMainChat, + e => { + if (!(e.ctrlKey || e.metaKey)) { + e.preventDefault(); + } + } +); diff --git a/common/app/redux/reducer.js b/common/app/redux/reducer.js index 1dbc462927..fd2ba31390 100644 --- a/common/app/redux/reducer.js +++ b/common/app/redux/reducer.js @@ -9,7 +9,8 @@ const initialState = { isSignedIn: false, csrfToken: '', windowHeight: 0, - navHeight: 0 + navHeight: 0, + isMainChatOpen: false }; export default handleActions( @@ -46,6 +47,10 @@ export default handleActions( ...state, isMapAlreadyLoaded: true, isMapDrawerOpen: !state.isMapDrawerOpen + }), + [types.toggleMainChat]: state => ({ + ...state, + isMainChatOpen: !state.isMainChatOpen }) }, initialState diff --git a/common/app/redux/types.js b/common/app/redux/types.js index f07780e2c0..28652d66ba 100644 --- a/common/app/redux/types.js +++ b/common/app/redux/types.js @@ -22,5 +22,11 @@ export default createTypes([ 'updateHikesData', // drawers - 'toggleMapDrawer' + 'toggleMapDrawer', + 'toggleWikiDrawer', + + // main chat + 'openMainChat', + 'closeMainChat', + 'toggleMainChat' ], 'app'); diff --git a/package.json b/package.json index 6b28695da3..18185ceef7 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "express-validator": "^2.18.0", "fetchr": "~0.5.12", "frameguard": "^2.0.0", + "gitter-sidecar": "^1.2.3", "helmet": "^2.0.0", "helmet-csp": "^1.0.3", "history": "^2.0.0",