aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNoah Loomans <noahloomans@gmail.com>2018-01-17 16:26:04 +0100
committerNoah Loomans <noahloomans@gmail.com>2018-01-17 16:26:04 +0100
commit1b3f4ea79f947558573fbce5a2e2d0c2c5dd6a8d (patch)
tree52a41cbd31a69de31edf83578e3055d076fa1c3e
parentc0aa588bc8f85b13b5a55ccd6cdf11bf99048a1c (diff)
Add view code
-rw-r--r--.eslintrc.js2
-rw-r--r--package.json1
-rw-r--r--src/client/react/actions/view.js28
-rw-r--r--src/client/react/components/container/Search.js21
-rw-r--r--src/client/react/components/container/View.js70
-rw-r--r--src/client/react/components/page/User.js2
-rw-r--r--src/client/react/index.js2
-rw-r--r--src/client/react/reducers.js2
-rw-r--r--src/client/react/reducers/view.js38
-rw-r--r--src/server/routes/getSchedule.js99
-rw-r--r--yarn.lock4
11 files changed, 210 insertions, 59 deletions
diff --git a/.eslintrc.js b/.eslintrc.js
index 376aa38..b253a04 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -8,5 +8,7 @@ module.exports = {
"rules": {
"react/jsx-filename-extension": ["error", { "extensions": [".js"] }],
"no-underscore-dangle": ["error", { "allow": ["_test"] }],
+ "class-methods-use-this": "off",
+ "no-prototype-builtins": "off",
}
};
diff --git a/package.json b/package.json
index 0363675..83d624b 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"css-loader": "^0.28.7",
"debug": "^2.6.0",
"diacritics": "^1.2.3",
+ "dompurify": "^1.0.3",
"encoding": "^0.1.12",
"eslint": "^4.14.0",
"express": "^4.13.4",
diff --git a/src/client/react/actions/view.js b/src/client/react/actions/view.js
new file mode 100644
index 0000000..f9f0be2
--- /dev/null
+++ b/src/client/react/actions/view.js
@@ -0,0 +1,28 @@
+// eslint-disable-next-line import/prefer-default-export
+export const fetchSchedule = user => (dispatch) => {
+ dispatch({
+ type: 'VIEW/FETCH_SCHEDULE_REQUEST',
+ user,
+ });
+
+ fetch(`/get/${user}`).then(
+ // success
+ (r) => {
+ r.text().then((htmlStr) => {
+ dispatch({
+ type: 'VIEW/FETCH_SCHEDULE_SUCCESS',
+ user,
+ htmlStr,
+ });
+ });
+ },
+
+ // error
+ () => {
+ dispatch({
+ type: 'VIEW/FETCH_SCHEDULE_FAILURE',
+ user,
+ });
+ },
+ );
+};
diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js
index 27b0563..9a99833 100644
--- a/src/client/react/components/container/Search.js
+++ b/src/client/react/components/container/Search.js
@@ -29,6 +29,12 @@ class Search extends React.Component {
this.props.dispatch(setUser(this.props.urlUser));
}
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.urlUser !== this.props.urlUser) {
+ this.props.dispatch(setUser(nextProps.urlUser));
+ }
+ }
+
onFocus() {
this.setState({
hasFocus: true,
@@ -51,10 +57,18 @@ class Search extends React.Component {
case 'ArrowDown':
this.props.dispatch(changeSelectedResult(+1));
break;
- case 'Enter':
- if (this.props.selectedResult) {
- this.props.history.push(`/${this.props.selectedResult}`);
+ case 'Enter': {
+ const result = this.props.selectedResult || this.props.results[0];
+
+ if (result === this.props.urlUser) {
+ // EDGE CASE: The user is set if the user changes, but it doesn't
+ // change if the result is already the one we are viewing.
+ // Therefor, we need to dispatch the SET_USER command manually.
+ this.props.dispatch(setUser(this.props.urlUser));
+ } else if (result) {
+ this.props.history.push(`/${result}`);
}
+ }
break;
default:
throw new Error('This should never happen... pls?');
@@ -100,6 +114,7 @@ class Search extends React.Component {
}
Search.propTypes = {
+ results: PropTypes.arrayOf(PropTypes.string).isRequired,
selectedResult: PropTypes.string,
urlUser: PropTypes.string,
isExactMatch: PropTypes.bool.isRequired,
diff --git a/src/client/react/components/container/View.js b/src/client/react/components/container/View.js
new file mode 100644
index 0000000..9bac66f
--- /dev/null
+++ b/src/client/react/components/container/View.js
@@ -0,0 +1,70 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import createDOMPurify from 'dompurify';
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
+
+import { fetchSchedule } from '../../actions/view';
+
+function cleanMeetingpointHTML(htmlStr) {
+ const DOMPurify = createDOMPurify(window);
+
+ return DOMPurify.sanitize(htmlStr, {
+ ADD_ATTR: ['rules'],
+ });
+}
+
+class View extends React.Component {
+ componentDidMount() {
+ if (!this.loadingFinished(this.props.user)) {
+ this.props.dispatch(fetchSchedule(this.props.user));
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.user !== this.props.user && !this.loadingFinished(nextProps.user)) {
+ this.props.dispatch(fetchSchedule(nextProps.user));
+ }
+ }
+
+ loadingFinished(user) {
+ return this.props.schedules.hasOwnProperty(user) &&
+ this.props.schedules[user].state === 'finished';
+ }
+
+ render() {
+ if (!this.loadingFinished(this.props.user)) {
+ return (
+ <div>
+ Loading...
+ </div>
+ );
+ }
+
+ const cleanHTML = cleanMeetingpointHTML(this.props.schedules[this.props.user].htmlStr);
+
+ return (
+ // eslint-disable-next-line react/no-danger
+ <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />
+ );
+ }
+}
+
+View.propTypes = {
+ user: PropTypes.string,
+ dispatch: PropTypes.func.isRequired,
+ schedules: PropTypes.objectOf(PropTypes.shape({
+ state: PropTypes.string.isRequired,
+ htmlStr: PropTypes.string,
+ })).isRequired,
+};
+
+View.defaultProps = {
+ user: null,
+};
+
+const mapStateToProps = state => ({
+ schedules: state.view.schedules,
+});
+
+export default withRouter(connect(mapStateToProps)(View));
diff --git a/src/client/react/components/page/User.js b/src/client/react/components/page/User.js
index 2ad65a6..ea8cd10 100644
--- a/src/client/react/components/page/User.js
+++ b/src/client/react/components/page/User.js
@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import Search from '../container/Search';
+import View from '../container/View';
import users from '../../users';
const App = ({ match }) => {
@@ -15,6 +16,7 @@ const App = ({ match }) => {
return (
<div>
<Search urlUser={user} />
+ <View user={user} />
</div>
);
};
diff --git a/src/client/react/index.js b/src/client/react/index.js
index ffa5403..a7006d4 100644
--- a/src/client/react/index.js
+++ b/src/client/react/index.js
@@ -13,7 +13,7 @@ import User from './components/page/User';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
- composeEnhancers(applyMiddleware(logger, thunk)),
+ composeEnhancers(applyMiddleware(thunk, logger)),
);
ReactDOM.render(
diff --git a/src/client/react/reducers.js b/src/client/react/reducers.js
index 9fdf2c4..fb97228 100644
--- a/src/client/react/reducers.js
+++ b/src/client/react/reducers.js
@@ -1,8 +1,10 @@
import { combineReducers } from 'redux';
import search from './reducers/search';
+import view from './reducers/view';
const rootReducer = combineReducers({
search,
+ view,
});
export default rootReducer;
diff --git a/src/client/react/reducers/view.js b/src/client/react/reducers/view.js
new file mode 100644
index 0000000..276d8ae
--- /dev/null
+++ b/src/client/react/reducers/view.js
@@ -0,0 +1,38 @@
+const DEFAULT_STATE = {
+ schedules: {},
+};
+
+const view = (state = DEFAULT_STATE, action) => {
+ switch (action.type) {
+ case 'VIEW/FETCH_SCHEDULE_REQUEST':
+ return {
+ ...state,
+ schedules: {
+ ...state.schedules,
+ [action.user]: {
+ state: 'fetching',
+ },
+ },
+ };
+ case 'VIEW/FETCH_SCHEDULE_SUCCESS':
+ return {
+ ...state,
+ schedules: {
+ ...state.schedules,
+ [action.user]: {
+ ...state.schedules[action.user],
+ state: 'finished',
+ htmlStr: action.htmlStr,
+ },
+ },
+ };
+ default:
+ return state;
+ }
+};
+
+export default view;
+
+export const _test = {
+ DEFAULT_STATE,
+};
diff --git a/src/server/routes/getSchedule.js b/src/server/routes/getSchedule.js
index 9c31d66..f6bc256 100644
--- a/src/server/routes/getSchedule.js
+++ b/src/server/routes/getSchedule.js
@@ -1,81 +1,70 @@
-const express = require('express')
-const router = express.Router()
-const request = require('request')
-const iconv = require('iconv-lite')
-const webshot = require('webshot')
+const express = require('express');
-const getUserIndex = require('../lib/getUserIndex')
-const getURLOfUser = require('../lib/getURLOfUser')
+const router = express.Router();
+const request = require('request');
+const iconv = require('iconv-lite');
+const webshot = require('webshot');
+
+const getUserIndex = require('../lib/getUserIndex');
+const getURLOfUser = require('../lib/getURLOfUser');
// copied from http://www.meetingpointmco.nl/Roosters-AL/doc/dagroosters/untisscripts.js,
// were using the same code as they do to be sure that we always get the same
// week number.
-function getWeekNumber (target) {
- const dayNr = (target.getDay() + 6) % 7
- target.setDate(target.getDate() - dayNr + 3)
- const firstThursday = target.valueOf()
- target.setMonth(0, 1)
+function getWeekNumber(target) {
+ const dayNr = (target.getDay() + 6) % 7;
+ // eslint-disable-next-line
+ target.setDate(target.getDate() - dayNr + 3);
+ const firstThursday = target.valueOf();
+ target.setMonth(0, 1);
if (target.getDay() !== 4) {
- target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7)
+ // eslint-disable-next-line
+ target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7);
}
- return 1 + Math.ceil((firstThursday - target) / 604800000)
+ return 1 + Math.ceil((firstThursday - target) / 604800000);
}
-router.get('/:type/:value.png', function (req, res, next) {
- port = process.env.PORT || 3000;
- const { type, value } = req.params
- const stream = webshot(
- `http://localhost:${port}/get/${type}/${value}`,
- { customCSS: "body { background-color: white; }" }
- )
- stream.pipe(res)
-})
-
-router.get('/:type/:value.jpg', function (req, res, next) {
- port = process.env.PORT || 3000;
- const { type, value } = req.params
- const stream = webshot(
- `http://localhost:${port}/get/${type}/${value}`,
- { customCSS: "body { background-color: white; }", streamType: 'jpg' }
- )
- stream.pipe(res)
-})
+// router.get('/:type/:value.png', (req, res) => {
+// const port = process.env.PORT || 3000;
+// const { type, value } = req.params;
+// const stream = webshot(
+// `http://localhost:${port}/get/${type}/${value}`,
+// { customCSS: 'body { background-color: white; }' },
+// );
+// stream.pipe(res);
+// });
-router.get('/:type/:value', function (req, res, next) {
- getUserIndex().then(users => {
- const { type, value } = req.params
- let { week } = req.query
+router.get('/:type/:value', (req, res, next) => {
+ getUserIndex().then((users) => {
+ const { type, value } = req.params;
+ let { week } = req.query;
const user =
- users.filter(user => user.type === type && user.value === value)[0]
+ users.filter(user_ => user_.type === type && user_.value === value)[0];
if (!user) {
- next(new Error(`${type}${value} is not in the user index.`))
+ next(new Error(`${type}${value} is not in the user index.`));
}
if (!week) {
- week = getWeekNumber(new Date())
+ week = getWeekNumber(new Date());
}
- const { index } = user
+ const { index } = user;
- const url = getURLOfUser(type, index, week)
+ const url = getURLOfUser(type, index, week);
- request(url, { encoding: null }, function (err, data) {
+ request(url, { encoding: null }, (err, data) => {
if (err) {
- next(err)
- return
+ next(err);
+ return;
}
- let utf8Body = iconv.decode(data.body, 'ISO-8859-1')
-
- users.forEach(function (user) {
- let utf8Body = utf8Body.replace(/oko/g, "test")
- })
+ const utf8Body = iconv.decode(data.body, 'ISO-8859-1');
- res.status(data.statusCode).end(utf8Body)
- })
- })
-})
+ res.status(data.statusCode).end(utf8Body);
+ });
+ });
+});
-module.exports = router
+module.exports = router;
diff --git a/yarn.lock b/yarn.lock
index 1db80c0..1886b4b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2113,6 +2113,10 @@ domhandler@^2.3.0:
dependencies:
domelementtype "1"
+dompurify@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-1.0.3.tgz#3f2f6ecb6ecd27599a506b410ff47d6eb90fd05d"
+
domutils@1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"