<commit_before>const Rx = require('rx')
const Lien = require("lien");
const uuid = require('uuid/v4');
const jsonfile = require('jsonfile')
const path = require('path')

const ALIAS_FILE = path.join(__dirname, '../../webhooks-alias.json')
const PORT = 3000 //TODO: move to config

let server = new Lien({
    host: "localhost",
    port: PORT
});

const source$ = new Rx.Subject()

const aliases = jsonfile.readFileSync(ALIAS_FILE)

const addEndpoint = (endpoint, alias) => {
  server.addPage("/" + endpoint, "post", l => {
    source$.onNext({ data: l.req.body, endpoint, alias})
    l.end("Got it!")
  })
  console.log(`Created endpoint at: ${endpoint} alias to ${alias}`)
}

aliases.forEach(([endpoint, alias]) => {
  addEndpoint(endpoint, alias)
})

server.on("load", err => {
    console.log(err || `Server started on port ${PORT}.`);
    err && process.exit(1);
});

server.addPage("/", "post", lien => {
  if(!lien.req.body.alias){
    lien.end(`Missing "alias" string in POST request`)
    return;
  }
  if(aliases.map(x => x[1]).indexOf(lien.req.body.alias) > -1){
    lien.end(`Duplicate "alias". Maybe try "${lien.req.body.alias}-2"`)
    return;
  }
  const endpoint = uuid();
  aliases.push([endpoint, lien.req.body.alias])
  jsonfile.writeFile(ALIAS_FILE, aliases)

  addEndpoint(endpoint, lien.req.body.alias)

  lien.end("Added enpoint at " + endpoint)
})

module.exports = source$<commit_msg>DO IP address<commit_after>const Rx = require('rx')
const Lien = require("lien");
const uuid = require('uuid/v4');
const jsonfile = require('jsonfile')
const path = require('path')

const ALIAS_FILE = path.join(__dirname, '../../webhooks-alias.json')
const PORT = 3000 //TODO: move to config

let server = new Lien({
    host: "0.0.0.0",  // DigitalOcean can't use anything else
    port: PORT
});

const source$ = new Rx.Subject()

const aliases = jsonfile.readFileSync(ALIAS_FILE)

const addEndpoint = (endpoint, alias) => {
  server.addPage("/" + endpoint, "post", l => {
    source$.onNext({ data: l.req.body, endpoint, alias})
    l.end("Got it!")
  })
  console.log(`Created endpoint at: ${endpoint} alias to ${alias}`)
}

aliases.forEach(([endpoint, alias]) => {
  addEndpoint(endpoint, alias)
})

server.on("load", err => {
    console.log(err || `Server started on port ${PORT}.`);
    err && process.exit(1);
});

server.addPage("/", "post", lien => {
  if(!lien.req.body.alias){
    lien.end(`Missing "alias" string in POST request`)
    return;
  }
  if(aliases.map(x => x[1]).indexOf(lien.req.body.alias) > -1){
    lien.end(`Duplicate "alias". Maybe try "${lien.req.body.alias}-2"`)
    return;
  }
  const endpoint = uuid();
  aliases.push([endpoint, lien.req.body.alias])
  jsonfile.writeFile(ALIAS_FILE, aliases)

  addEndpoint(endpoint, lien.req.body.alias)

  lien.end("Added enpoint at " + endpoint)
})

module.exports = source$<|endoftext|>


<commit_before>module.exports = function(app) {
  app.use(require('express').static(__dirname + '/../'));
  app.get('/', function(req, res) {
    res.redirect('dist/tests/');
  })
};
<commit_msg>Ensure files are served through Broccoli middleware<commit_after>module.exports = function(app) {
  app.get('/', function(req, res) {
    res.redirect('tests/');
  });
};
<|endoftext|>


<commit_before>const parser = require('ua-parser-js');

class SnapdropServer {

    constructor(port) {
        const WebSocket = require('ws');
        this._wss = new WebSocket.Server({
            port: port
        });
        this._wss.on('connection', (socket, request) => this._onConnection(new Peer(socket, request)));
        this._wss.on('headers', (headers, response) => this._onHeaders(headers, response));

        this._rooms = {};
        this._timerID = 0;

        console.log('Snapdrop is running on port', port);
    }

    _onConnection(peer) {
        this._joinRoom(peer);
        peer.socket.on('message', message => this._onMessage(peer, message));
        this._keepAlive(peer);
    }

    _onHeaders(headers, response) {
        if (response.headers.cookie && response.headers.cookie.indexOf('peerid=') > -1) return;
        response.peerId = Peer.uuid();
        headers.push('Set-Cookie: peerid=' + response.peerId);
    }

    _onMessage(sender, message) {
        message = JSON.parse(message);

        switch (message.type) {
            case 'disconnect':
                this._leaveRoom(sender);
                break;
            case 'pong':
                sender.lastBeat = Date.now();
                break;
        }

        // relay message to recipient
        if (message.to && this._rooms[sender.ip]) {
            const recipientId = message.to; // TODO: sanitize
            const recipient = this._rooms[sender.ip][recipientId];
            delete message.to;
            // add sender id
            message.sender = sender.id;
            this._send(recipient, message);
            return;
        }
    }

    _joinRoom(peer) {
        // if room doesn't exist, create it
        if (!this._rooms[peer.ip]) {
            this._rooms[peer.ip] = {};
        }
        if (this._rooms[peer.ip][peer.id]) {
            this._cancelKeepAlive(this._rooms[peer.ip][peer.id]);
        }

        // console.log(peer.id, ' joined the room', peer.ip);
        // notify all other peers
        for (const otherPeerId in this._rooms[peer.ip]) {
            const otherPeer = this._rooms[peer.ip][otherPeerId];
            this._send(otherPeer, {
                type: 'peer-joined',
                peer: peer.getInfo()
            });
        }

        // notify peer about the other peers
        const otherPeers = [];
        for (const otherPeerId in this._rooms[peer.ip]) {
            otherPeers.push(this._rooms[peer.ip][otherPeerId].getInfo());
        }

        this._send(peer, {
            type: 'peers',
            peers: otherPeers
        });

        // add peer to room
        this._rooms[peer.ip][peer.id] = peer;
    }

    _leaveRoom(peer) {
        if (!this._rooms[peer.ip] || !this._rooms[peer.ip][peer.id]) return;
        this._cancelKeepAlive(peer);

        // delete the peer
        delete this._rooms[peer.ip][peer.id];

        peer.socket.terminate();
        //if room is empty, delete the room
        if (!Object.keys(this._rooms[peer.ip]).length) {
            delete this._rooms[peer.ip];
        } else {
            // notify all other peers
            for (const otherPeerId in this._rooms[peer.ip]) {
                const otherPeer = this._rooms[peer.ip][otherPeerId];
                this._send(otherPeer, {
                    type: 'peer-left',
                    peerId: peer.id
                });
            }
        }
    }

    _send(peer, message) {
        if (!peer) return console.error('undefined peer');
        if (this._wss.readyState !== this._wss.OPEN) return console.error('Socket is closed');
        message = JSON.stringify(message);
        peer.socket.send(message, error => {
            if (error) this._leaveRoom(peer);
        });
    }

    _keepAlive(peer) {
        var timeout = 10000;
        if (!peer.lastBeat) {
            peer.lastBeat = Date.now();
        }
        if (Date.now() - peer.lastBeat > 2 * timeout) {
            this._leaveRoom(peer);
            return;
        }

        this._send(peer, { type: 'ping' });

        this._cancelKeepAlive(peer);
        peer.timerId = setTimeout(() => this._keepAlive(peer), timeout);
    }

    _cancelKeepAlive(peer) {
        if (peer.timerId) {
            clearTimeout(peer.timerId);
        }
    }
}

class Peer {

    constructor(socket, request) {
        // set socket
        this.socket = socket;

        // set remote ip
        if (request.headers['x-forwarded-for'])
            this.ip = request.headers['x-forwarded-for'].split(/\s*,\s*/)[0];
        else
            this.ip = request.connection.remoteAddress;

        if (request.peerId) {
            this.id = request.peerId;
        } else {
            this.id = request.headers.cookie.replace('peerid=', '');
        }
        // set peer id
        // is WebRTC supported ?
        this.rtcSupported = request.url.indexOf('webrtc') > -1;
        // set name
        this.setName(request);
        // for keepalive
        this.timerId = 0;
        this.lastBeat = Date.now();
    }

    // return uuid of form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
    static uuid() {
        let uuid = '',
            ii;
        for (ii = 0; ii < 32; ii += 1) {
            switch (ii) {
                case 8:
                case 20:
                    uuid += '-';
                    uuid += (Math.random() * 16 | 0).toString(16);
                    break;
                case 12:
                    uuid += '-';
                    uuid += '4';
                    break;
                case 16:
                    uuid += '-';
                    uuid += (Math.random() * 4 | 8).toString(16);
                    break;
                default:
                    uuid += (Math.random() * 16 | 0).toString(16);
            }
        }
        return uuid;
    };

    toString() {
        return `<Peer id=${this.id} ip=${this.ip} rtcSupported=${this.rtcSupported}>`
    }

    setName(req) {
        var ua = parser(req.headers['user-agent']);
        this.name = {
            model: ua.device.model,
            os: ua.os.name,
            browser: ua.browser.name,
            type: ua.device.type
        };
    }

    getInfo() {
        return {
            id: this.id,
            name: this.name,
            rtcSupported: this.rtcSupported
        }
    }
}

const server = new SnapdropServer(process.env.PORT || 3000);<commit_msg>Cancel keep alive on join room<commit_after>const parser = require('ua-parser-js');

class SnapdropServer {

    constructor(port) {
        const WebSocket = require('ws');
        this._wss = new WebSocket.Server({
            port: port
        });
        this._wss.on('connection', (socket, request) => this._onConnection(new Peer(socket, request)));
        this._wss.on('headers', (headers, response) => this._onHeaders(headers, response));

        this._rooms = {};

        console.log('Snapdrop is running on port', port);
    }

    _onConnection(peer) {
        this._joinRoom(peer);
        peer.socket.on('message', message => this._onMessage(peer, message));
        this._keepAlive(peer);
    }

    _onHeaders(headers, response) {
        if (response.headers.cookie && response.headers.cookie.indexOf('peerid=') > -1) return;
        response.peerId = Peer.uuid();
        headers.push('Set-Cookie: peerid=' + response.peerId);
    }

    _onMessage(sender, message) {
        message = JSON.parse(message);

        switch (message.type) {
            case 'disconnect':
                this._leaveRoom(sender);
                break;
            case 'pong':
                sender.lastBeat = Date.now();
                break;
        }

        // relay message to recipient
        if (message.to && this._rooms[sender.ip]) {
            const recipientId = message.to; // TODO: sanitize
            const recipient = this._rooms[sender.ip][recipientId];
            delete message.to;
            // add sender id
            message.sender = sender.id;
            this._send(recipient, message);
            return;
        }
    }

    _joinRoom(peer) {
        // if room doesn't exist, create it
        if (!this._rooms[peer.ip]) {
            this._rooms[peer.ip] = {};
        }
        if (this._rooms[peer.ip][peer.id]) {
            this._cancelKeepAlive(this._rooms[peer.ip][peer.id]);
        }

        // console.log(peer.id, ' joined the room', peer.ip);
        // notify all other peers
        for (const otherPeerId in this._rooms[peer.ip]) {
            const otherPeer = this._rooms[peer.ip][otherPeerId];
            this._send(otherPeer, {
                type: 'peer-joined',
                peer: peer.getInfo()
            });
        }

        // notify peer about the other peers
        const otherPeers = [];
        for (const otherPeerId in this._rooms[peer.ip]) {
            otherPeers.push(this._rooms[peer.ip][otherPeerId].getInfo());
        }

        this._send(peer, {
            type: 'peers',
            peers: otherPeers
        });

        // add peer to room
        this._rooms[peer.ip][peer.id] = peer;
    }

    _leaveRoom(peer) {
        if (!this._rooms[peer.ip] || !this._rooms[peer.ip][peer.id]) return;
        this._cancelKeepAlive(peer);

        // delete the peer
        delete this._rooms[peer.ip][peer.id];

        peer.socket.terminate();
        //if room is empty, delete the room
        if (!Object.keys(this._rooms[peer.ip]).length) {
            delete this._rooms[peer.ip];
        } else {
            // notify all other peers
            for (const otherPeerId in this._rooms[peer.ip]) {
                const otherPeer = this._rooms[peer.ip][otherPeerId];
                this._send(otherPeer, {
                    type: 'peer-left',
                    peerId: peer.id
                });
            }
        }
    }

    _send(peer, message) {
        if (!peer) return console.error('undefined peer');
        if (this._wss.readyState !== this._wss.OPEN) return console.error('Socket is closed');
        message = JSON.stringify(message);
        peer.socket.send(message, error => {
            if (error) this._leaveRoom(peer);
        });
    }

    _keepAlive(peer) {
        this._cancelKeepAlive(peer);
        var timeout = 10000;
        if (!peer.lastBeat) {
            peer.lastBeat = Date.now();
        }
        if (Date.now() - peer.lastBeat > 2 * timeout) {
            this._leaveRoom(peer);
            return;
        }

        this._send(peer, { type: 'ping' });

        peer.timerId = setTimeout(() => this._keepAlive(peer), timeout);
    }

    _cancelKeepAlive(peer) {
        if (peer.timerId) {
            clearTimeout(peer.timerId);
        }
    }
}

class Peer {

    constructor(socket, request) {
        // set socket
        this.socket = socket;

        // set remote ip
        if (request.headers['x-forwarded-for'])
            this.ip = request.headers['x-forwarded-for'].split(/\s*,\s*/)[0];
        else
            this.ip = request.connection.remoteAddress;

        if (request.peerId) {
            this.id = request.peerId;
        } else {
            this.id = request.headers.cookie.replace('peerid=', '');
        }
        // set peer id
        // is WebRTC supported ?
        this.rtcSupported = request.url.indexOf('webrtc') > -1;
        // set name
        this.setName(request);
        // for keepalive
        this.timerId = 0;
        this.lastBeat = Date.now();
    }

    // return uuid of form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
    static uuid() {
        let uuid = '',
            ii;
        for (ii = 0; ii < 32; ii += 1) {
            switch (ii) {
                case 8:
                case 20:
                    uuid += '-';
                    uuid += (Math.random() * 16 | 0).toString(16);
                    break;
                case 12:
                    uuid += '-';
                    uuid += '4';
                    break;
                case 16:
                    uuid += '-';
                    uuid += (Math.random() * 4 | 8).toString(16);
                    break;
                default:
                    uuid += (Math.random() * 16 | 0).toString(16);
            }
        }
        return uuid;
    };

    toString() {
        return `<Peer id=${this.id} ip=${this.ip} rtcSupported=${this.rtcSupported}>`
    }

    setName(req) {
        var ua = parser(req.headers['user-agent']);
        this.name = {
            model: ua.device.model,
            os: ua.os.name,
            browser: ua.browser.name,
            type: ua.device.type
        };
    }

    getInfo() {
        return {
            id: this.id,
            name: this.name,
            rtcSupported: this.rtcSupported
        }
    }
}

const server = new SnapdropServer(process.env.PORT || 3000);<|endoftext|>


<commit_before>const moment = require('moment');
const express = require('express');
const shortId = require('shortid');
const router = express.Router({ mergeParams: true });
const Nexboard = require("nexboard-api-js");
const api = require('../api');
const authHelper = require('../helpers/authentication');
const winston = require('winston');
const logger = winston.createLogger({
    transports: [
        new winston.transports.Console({
            format: winston.format.combine(
                winston.format.colorize(),
                winston.format.simple()
            )
        })
    ]
});

const etherpadBaseUrl = process.env.ETHERPAD_BASE_URL || 'https://etherpad.schul-cloud.org/etherpad/p/';

const editTopicHandler = (req, res, next) => {
    const context = req.originalUrl.split('/')[1];
    let lessonPromise, action, method;
    if (req.params.topicId) {
        action = `/${context}/`
            + (context === 'courses' ? req.params.courseId : req.params.teamId)
            + '/topics/' + req.params.topicId
            + (req.query.courseGroup ? '?courseGroup=' + req.query.courseGroup : '');
        method = 'patch';
        lessonPromise = api(req).get('/lessons/' + req.params.topicId);
    } else {
        action = `/${context}/`
            + (context === 'courses' ? req.params.courseId : req.params.teamId)
            + '/topics'
            + (req.query.courseGroup ? '?courseGroup=' + req.query.courseGroup : '');
        method = 'post';
        lessonPromise = Promise.resolve({});
    }

    lessonPromise.then(lesson => {
        if (lesson.contents) {
            // so we can share the content through data-value to the react component
            lesson.contents = JSON.stringify(lesson.contents);
        }

        res.render('topic/edit-topic', {
            action,
            method,
            title: req.params.topicId ? 'Thema bearbeiten' : 'Thema anlegen',
            submitLabel: req.params.topicId ? 'Änderungen speichern' : 'Thema anlegen',
            closeLabel: 'Abbrechen',
            lesson,
            courseId: req.params.courseId,
            teamId: req.params.teamId,
            courseGroupId: req.query.courseGroup,
            etherpadBaseUrl: etherpadBaseUrl
        });
    }).catch(err => {
        next(err);
    });
};

const checkInternalComponents = (data, baseUrl) => {
    let pattern = new RegExp(`(${baseUrl})(?!.*\/(edit|new|add|files\/my|files\/file|account|administration|topics)).*`);
    (data.contents || []).map(c => {
        if (c.component === 'internal' && !pattern.test((c.content || {}).url)) {
            (c.content || {}).url = baseUrl;
        }
    });
};

// secure routes
router.use(authHelper.authChecker);

router.get('/', (req, res, next) => {
    const context = req.originalUrl.split('/')[1];
    res.redirect(`/${context}/` + req.params.courseId);
});

router.get('/add', editTopicHandler);

router.post('/', async function (req, res, next) {
    const context = req.originalUrl.split('/')[1];
    const data = req.body;

    // Check for neXboard compontent
    data.contents = await createNewNexBoards(req, res, data.contents);

    data.contents = data.contents.filter(c => { return c !== undefined; });

    data.time = moment(data.time || 0, 'HH:mm').toString();
    data.date = moment(data.date || 0, 'YYYY-MM-DD').toString();

    req.query.courseGroup ? '' : delete data.courseGroupId;

    // recheck internal components by pattern
    checkInternalComponents(data, req.headers.origin);

    api(req).post('/lessons/', {
        json: data // TODO: sanitize
    }).then(_ => {
        res.redirect(
            context === 'courses'
                ? `/courses/` + req.params.courseId +
                (req.query.courseGroup ? '/groups/' + req.query.courseGroup : '')
                : `/teams/` + req.params.teamId + '/topics'
        );
    }).catch(_ => {
        res.sendStatus(500);
    });
});

router.post('/:id/share', function (req, res, next) {
    // if lesson already has shareToken, do not generate a new one
    api(req).get('/lessons/' + req.params.id).then(topic => {
        topic.shareToken = topic.shareToken || shortId.generate();
        api(req).patch("/lessons/" + req.params.id, { json: topic })
            .then(result => res.json(result))
            .catch(err => { res.err(err); });
    });
});

router.get('/:topicId', function (req, res, next) {
    if (req.query.edtr) {
        return res.render('topic/topic-edtr', {
            edtrSource: req.query.version === 'B' ? process.env.EDTR_SOURCE_B : process.env.EDTR_SOURCE || "https://cdn.jsdelivr.net/gh/schul-cloud/edtrio@4d16b968e217359d958d828155a91acb4295d94c/dist/index.js",
            backendUrl: process.env.PUBLIC_BACKEND_URL || "http://localhost:3030",
        })
    }

    const context = req.originalUrl.split('/')[1];
    Promise.all([
        api(req).get(`/${context}/` + req.params.courseId),
        api(req).get('/lessons/' + req.params.topicId, {
            qs: {
                $populate: ['materialIds']
            }
        }),
        api(req).get('/homework/', {
            qs: {
                courseId: req.params.courseId,
                lessonId: req.params.topicId,
                $populate: ['courseId'],
                archived: { $ne: res.locals.currentUser._id }
            }
        }),
        req.query.courseGroup ?
            api(req).get('/courseGroups/' + req.query.courseGroup) :
            Promise.resolve({})
    ]).then(([course, lesson, homeworks, courseGroup]) => {
        // decorate contents
        lesson.contents = (lesson.contents || []).map(block => {
            block.component = 'topic/components/content-' + block.component;
            return block;
        });
        homeworks = (homeworks.data || []).map(assignment => {
            assignment.url = '/homework/' + assignment._id;
            return assignment;
        });
        homeworks.sort((a, b) => {
            if (a.dueDate > b.dueDate) {
                return 1;
            } else {
                return -1;
            }
        });
        res.render('topic/topic', Object.assign({}, lesson, {
            title: lesson.name,
            context,
            homeworks: homeworks.filter(function (task) { return !task.private; }),
            myhomeworks: homeworks.filter(function (task) { return task.private; }),
            courseId: req.params.courseId,
            isCourseGroupTopic: courseGroup._id !== undefined,
            breadcrumb: [{
                title: 'Meine Kurse',
                url: `/${context}`
            },
            {
                title: course.name + ' ' + '> Themen',
                url: `/${context}/` + course._id
            },
            {
                title: lesson.name,
                url: `/${context}/` + course._id + '/topics/' + lesson._id
            },
            courseGroup._id ? {
                title: courseGroup.name,
                url: `/${context}/` + course._id + '/groups/' + courseGroup._id
            } : {}
            ]
        }), (error, html) => {
            if (error) {
                throw 'error in GET /:topicId - res.render: ' + error;
            }
            res.send(html);
        });
    }).catch(err => {
        next(err);
    });
});

router.patch('/:topicId', async function (req, res, next) {
    const context = req.originalUrl.split('/')[1];
    const data = req.body;
    data.time = moment(data.time || 0, 'HH:mm').toString();
    data.date = moment(data.date || 0, 'YYYY-MM-DD').toString();

    if (!data.courseId && !req.query.courseGroup) {
        data.courseId = req.params.courseId;
    }

    // if not a simple hidden or position patch, set contents to empty array
    if (!data.contents && !req.query.json) {
        data.contents = [];
    }

    req.query.courseGroup ? '' : delete data.courseGroupId;

    // create new Nexboard when necessary, if not simple hidden or position patch
    data.contents ? data.contents = await createNewNexBoards(req, res, data.contents) : '';

    if (data.contents)
        data.contents = data.contents.filter(c => { return c !== undefined; });

    // recheck internal components by pattern
    checkInternalComponents(data, req.headers.origin);

    api(req).patch('/lessons/' + req.params.topicId, {
        json: data // TODO: sanitize
    }).then(_ => {
        if (req.query.json) {
            res.json(_);
        } else {
            //sends a GET request, not a PATCH
            res.redirect(`/${context}/` + req.params.courseId + '/topics/' + req.params.topicId +
                (req.query.courseGroup ? '?courseGroup=' + req.query.courseGroup : ''));
        }
    }).catch(_ => {
        res.sendStatus(500);
    });
});

router.delete('/:topicId', function (req, res, next) {
    api(req).delete('/lessons/' + req.params.topicId).then(_ => {
        res.sendStatus(200);
    }).catch(err => {
        next(err);
    });
});

router.delete('/:topicId/materials/:materialId', function (req, res, next) {
    api(req).patch('/lessons/' + req.params.topicId, {
        json: {
            courseId: req.params.courseId,
            $pull: {
                materialIds: req.params.materialId
            }
        }
    }).then(_ => {
        api(req).delete('/materials/' + req.params.materialId).then(_ => {
            res.sendStatus(200);
        });
    });
});

router.get('/:topicId/edit', editTopicHandler);

async function createNewNexBoards(req, res, contents = []) {
    return await Promise.all(contents.map(async content => {
        if (content.component === "neXboard" && content.content.board === '0') {
            try {
                const board = await getNexBoardAPI().createBoard(
                    content.content.title,
                    content.content.description,
                    await getNexBoardProjectFromUser(req, res.locals.currentUser),
                    'schulcloud');

                content.content.title = board.title;
                content.content.board = board.id;
                content.content.url = board.publicLink;
                content.content.description = board.description;

                return content;

            } catch (err) {
                logger.error(err);

                return undefined;
            }
        } else
            return content;
    }));
}

const getNexBoardAPI = () => {
    if (!process.env.NEXBOARD_USER_ID && !process.env.NEXBOARD_API_KEY) {
        logger.error('nexBoard env is currently not defined.');
    }
    return new Nexboard(process.env.NEXBOARD_API_KEY, process.env.NEXBOARD_USER_ID);
};

const getNexBoardProjectFromUser = async (req, user) => {
    const preferences = user.preferences || {};
    if (typeof preferences.nexBoardProjectID === 'undefined') {
        const project = await getNexBoardAPI().createProject(user._id, user._id);
        preferences.nexBoardProjectID = project.id;
        api(req).patch('/users/' + user._id, { json: { preferences } });
    }
    return preferences.nexBoardProjectID;
};

const getNexBoards = (req, res, next) => {
    api(req).get('/lessons/contents/neXboard', {
        qs: {
            type: 'neXboard',
            user: res.locals.currentUser._id
        }
    })
        .then(boards => {
            res.json(boards);
        });
};

router.get('/:topicId/nexboard/boards', getNexBoards);

router.get('/nexboard/boards', getNexBoards);

module.exports = router;
<commit_msg>enable dynamic edtr_source<commit_after>const moment = require('moment');
const express = require('express');
const shortId = require('shortid');
const router = express.Router({ mergeParams: true });
const Nexboard = require("nexboard-api-js");
const api = require('../api');
const authHelper = require('../helpers/authentication');
const winston = require('winston');
const logger = winston.createLogger({
    transports: [
        new winston.transports.Console({
            format: winston.format.combine(
                winston.format.colorize(),
                winston.format.simple()
            )
        })
    ]
});

const etherpadBaseUrl = process.env.ETHERPAD_BASE_URL || 'https://etherpad.schul-cloud.org/etherpad/p/';

const editTopicHandler = (req, res, next) => {
    const context = req.originalUrl.split('/')[1];
    let lessonPromise, action, method;
    if (req.params.topicId) {
        action = `/${context}/`
            + (context === 'courses' ? req.params.courseId : req.params.teamId)
            + '/topics/' + req.params.topicId
            + (req.query.courseGroup ? '?courseGroup=' + req.query.courseGroup : '');
        method = 'patch';
        lessonPromise = api(req).get('/lessons/' + req.params.topicId);
    } else {
        action = `/${context}/`
            + (context === 'courses' ? req.params.courseId : req.params.teamId)
            + '/topics'
            + (req.query.courseGroup ? '?courseGroup=' + req.query.courseGroup : '');
        method = 'post';
        lessonPromise = Promise.resolve({});
    }

    lessonPromise.then(lesson => {
        if (lesson.contents) {
            // so we can share the content through data-value to the react component
            lesson.contents = JSON.stringify(lesson.contents);
        }

        res.render('topic/edit-topic', {
            action,
            method,
            title: req.params.topicId ? 'Thema bearbeiten' : 'Thema anlegen',
            submitLabel: req.params.topicId ? 'Änderungen speichern' : 'Thema anlegen',
            closeLabel: 'Abbrechen',
            lesson,
            courseId: req.params.courseId,
            teamId: req.params.teamId,
            courseGroupId: req.query.courseGroup,
            etherpadBaseUrl: etherpadBaseUrl
        });
    }).catch(err => {
        next(err);
    });
};

const checkInternalComponents = (data, baseUrl) => {
    let pattern = new RegExp(`(${baseUrl})(?!.*\/(edit|new|add|files\/my|files\/file|account|administration|topics)).*`);
    (data.contents || []).map(c => {
        if (c.component === 'internal' && !pattern.test((c.content || {}).url)) {
            (c.content || {}).url = baseUrl;
        }
    });
};

// secure routes
router.use(authHelper.authChecker);

router.get('/', (req, res, next) => {
    const context = req.originalUrl.split('/')[1];
    res.redirect(`/${context}/` + req.params.courseId);
});

router.get('/add', editTopicHandler);

router.post('/', async function (req, res, next) {
    const context = req.originalUrl.split('/')[1];
    const data = req.body;

    // Check for neXboard compontent
    data.contents = await createNewNexBoards(req, res, data.contents);

    data.contents = data.contents.filter(c => { return c !== undefined; });

    data.time = moment(data.time || 0, 'HH:mm').toString();
    data.date = moment(data.date || 0, 'YYYY-MM-DD').toString();

    req.query.courseGroup ? '' : delete data.courseGroupId;

    // recheck internal components by pattern
    checkInternalComponents(data, req.headers.origin);

    api(req).post('/lessons/', {
        json: data // TODO: sanitize
    }).then(_ => {
        res.redirect(
            context === 'courses'
                ? `/courses/` + req.params.courseId +
                (req.query.courseGroup ? '/groups/' + req.query.courseGroup : '')
                : `/teams/` + req.params.teamId + '/topics'
        );
    }).catch(_ => {
        res.sendStatus(500);
    });
});

router.post('/:id/share', function (req, res, next) {
    // if lesson already has shareToken, do not generate a new one
    api(req).get('/lessons/' + req.params.id).then(topic => {
        topic.shareToken = topic.shareToken || shortId.generate();
        api(req).patch("/lessons/" + req.params.id, { json: topic })
            .then(result => res.json(result))
            .catch(err => { res.err(err); });
    });
});

router.get('/:topicId', function (req, res, next) {
    if (req.query.edtr || req.query.edtr_hash) {
        return res.render('topic/topic-edtr', {
            edtrSource: req.query.edtr_hash ? `https://cdn.jsdelivr.net/gh/schul-cloud/edtrio@${req.query.edtr_hash}/dist/index.js` : req.query.version === 'B' ? process.env.EDTR_SOURCE_B : process.env.EDTR_SOURCE || "https://cdn.jsdelivr.net/gh/schul-cloud/edtrio@4d16b968e217359d958d828155a91acb4295d94c/dist/index.js",
            backendUrl: process.env.PUBLIC_BACKEND_URL || "http://localhost:3030",
        })
    }

    const context = req.originalUrl.split('/')[1];
    Promise.all([
        api(req).get(`/${context}/` + req.params.courseId),
        api(req).get('/lessons/' + req.params.topicId, {
            qs: {
                $populate: ['materialIds']
            }
        }),
        api(req).get('/homework/', {
            qs: {
                courseId: req.params.courseId,
                lessonId: req.params.topicId,
                $populate: ['courseId'],
                archived: { $ne: res.locals.currentUser._id }
            }
        }),
        req.query.courseGroup ?
            api(req).get('/courseGroups/' + req.query.courseGroup) :
            Promise.resolve({})
    ]).then(([course, lesson, homeworks, courseGroup]) => {
        // decorate contents
        lesson.contents = (lesson.contents || []).map(block => {
            block.component = 'topic/components/content-' + block.component;
            return block;
        });
        homeworks = (homeworks.data || []).map(assignment => {
            assignment.url = '/homework/' + assignment._id;
            return assignment;
        });
        homeworks.sort((a, b) => {
            if (a.dueDate > b.dueDate) {
                return 1;
            } else {
                return -1;
            }
        });
        res.render('topic/topic', Object.assign({}, lesson, {
            title: lesson.name,
            context,
            homeworks: homeworks.filter(function (task) { return !task.private; }),
            myhomeworks: homeworks.filter(function (task) { return task.private; }),
            courseId: req.params.courseId,
            isCourseGroupTopic: courseGroup._id !== undefined,
            breadcrumb: [{
                title: 'Meine Kurse',
                url: `/${context}`
            },
            {
                title: course.name + ' ' + '> Themen',
                url: `/${context}/` + course._id
            },
            {
                title: lesson.name,
                url: `/${context}/` + course._id + '/topics/' + lesson._id
            },
            courseGroup._id ? {
                title: courseGroup.name,
                url: `/${context}/` + course._id + '/groups/' + courseGroup._id
            } : {}
            ]
        }), (error, html) => {
            if (error) {
                throw 'error in GET /:topicId - res.render: ' + error;
            }
            res.send(html);
        });
    }).catch(err => {
        next(err);
    });
});

router.patch('/:topicId', async function (req, res, next) {
    const context = req.originalUrl.split('/')[1];
    const data = req.body;
    data.time = moment(data.time || 0, 'HH:mm').toString();
    data.date = moment(data.date || 0, 'YYYY-MM-DD').toString();

    if (!data.courseId && !req.query.courseGroup) {
        data.courseId = req.params.courseId;
    }

    // if not a simple hidden or position patch, set contents to empty array
    if (!data.contents && !req.query.json) {
        data.contents = [];
    }

    req.query.courseGroup ? '' : delete data.courseGroupId;

    // create new Nexboard when necessary, if not simple hidden or position patch
    data.contents ? data.contents = await createNewNexBoards(req, res, data.contents) : '';

    if (data.contents)
        data.contents = data.contents.filter(c => { return c !== undefined; });

    // recheck internal components by pattern
    checkInternalComponents(data, req.headers.origin);

    api(req).patch('/lessons/' + req.params.topicId, {
        json: data // TODO: sanitize
    }).then(_ => {
        if (req.query.json) {
            res.json(_);
        } else {
            //sends a GET request, not a PATCH
            res.redirect(`/${context}/` + req.params.courseId + '/topics/' + req.params.topicId +
                (req.query.courseGroup ? '?courseGroup=' + req.query.courseGroup : ''));
        }
    }).catch(_ => {
        res.sendStatus(500);
    });
});

router.delete('/:topicId', function (req, res, next) {
    api(req).delete('/lessons/' + req.params.topicId).then(_ => {
        res.sendStatus(200);
    }).catch(err => {
        next(err);
    });
});

router.delete('/:topicId/materials/:materialId', function (req, res, next) {
    api(req).patch('/lessons/' + req.params.topicId, {
        json: {
            courseId: req.params.courseId,
            $pull: {
                materialIds: req.params.materialId
            }
        }
    }).then(_ => {
        api(req).delete('/materials/' + req.params.materialId).then(_ => {
            res.sendStatus(200);
        });
    });
});

router.get('/:topicId/edit', editTopicHandler);

async function createNewNexBoards(req, res, contents = []) {
    return await Promise.all(contents.map(async content => {
        if (content.component === "neXboard" && content.content.board === '0') {
            try {
                const board = await getNexBoardAPI().createBoard(
                    content.content.title,
                    content.content.description,
                    await getNexBoardProjectFromUser(req, res.locals.currentUser),
                    'schulcloud');

                content.content.title = board.title;
                content.content.board = board.id;
                content.content.url = board.publicLink;
                content.content.description = board.description;

                return content;

            } catch (err) {
                logger.error(err);

                return undefined;
            }
        } else
            return content;
    }));
}

const getNexBoardAPI = () => {
    if (!process.env.NEXBOARD_USER_ID && !process.env.NEXBOARD_API_KEY) {
        logger.error('nexBoard env is currently not defined.');
    }
    return new Nexboard(process.env.NEXBOARD_API_KEY, process.env.NEXBOARD_USER_ID);
};

const getNexBoardProjectFromUser = async (req, user) => {
    const preferences = user.preferences || {};
    if (typeof preferences.nexBoardProjectID === 'undefined') {
        const project = await getNexBoardAPI().createProject(user._id, user._id);
        preferences.nexBoardProjectID = project.id;
        api(req).patch('/users/' + user._id, { json: { preferences } });
    }
    return preferences.nexBoardProjectID;
};

const getNexBoards = (req, res, next) => {
    api(req).get('/lessons/contents/neXboard', {
        qs: {
            type: 'neXboard',
            user: res.locals.currentUser._id
        }
    })
        .then(boards => {
            res.json(boards);
        });
};

router.get('/:topicId/nexboard/boards', getNexBoards);

router.get('/nexboard/boards', getNexBoards);

module.exports = router;
<|endoftext|>


<commit_before>// @inheritedComponent Paper

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import withStyles from '../styles/withStyles';
import { capitalize } from '../utils/helpers';
import Paper from '../Paper';

export const styles = theme => {
  const backgroundColorDefault =
    theme.palette.type === 'light' ? theme.palette.grey[100] : theme.palette.grey[900];

  return {
    root: {
      display: 'flex',
      flexDirection: 'column',
      width: '100%',
      boxSizing: 'border-box', // Prevent padding issue with the Modal and fixed positioned AppBar.
      zIndex: theme.zIndex.appBar,
      flexShrink: 0,
    },
    positionFixed: {
      position: 'fixed',
      top: 0,
      left: 'auto',
      right: 0,
    },
    positionAbsolute: {
      position: 'absolute',
      top: 0,
      left: 'auto',
      right: 0,
    },
    positionStatic: {
      position: 'static',
      flexShrink: 0,
    },
    colorDefault: {
      backgroundColor: backgroundColorDefault,
      color: theme.palette.getContrastText(backgroundColorDefault),
    },
    colorPrimary: {
      backgroundColor: theme.palette.primary.main,
      color: theme.palette.primary.contrastText,
    },
    colorSecondary: {
      backgroundColor: theme.palette.secondary.main,
      color: theme.palette.secondary.contrastText,
    },
  };
};

function AppBar(props) {
  const { children, classes, className: classNameProp, color, position, ...other } = props;

  const className = classNames(
    classes.root,
    classes[`position${capitalize(position)}`],
    {
      [classes[`color${capitalize(color)}`]]: color !== 'inherit',
      'mui-fixed': position === 'fixed', // Useful for the Dialog
    },
    classNameProp,
  );

  return (
    <Paper square component="header" elevation={4} className={className} {...other}>
      {children}
    </Paper>
  );
}

AppBar.propTypes = {
  /**
   * The content of the component.
   */
  children: PropTypes.node.isRequired,
  /**
   * Useful to extend the style applied to components.
   */
  classes: PropTypes.object.isRequired,
  /**
   * @ignore
   */
  className: PropTypes.string,
  /**
   * The color of the component. It's using the theme palette when that makes sense.
   */
  color: PropTypes.oneOf(['inherit', 'primary', 'secondary', 'default']),
  /**
   * The positioning type.
   */
  position: PropTypes.oneOf(['static', 'fixed', 'absolute']),
};

AppBar.defaultProps = {
  color: 'primary',
  position: 'fixed',
};

export default withStyles(styles, { name: 'MuiAppBar' })(AppBar);
<commit_msg>[AppBar] Remove one dead CSS property (#10096)<commit_after>// @inheritedComponent Paper

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import withStyles from '../styles/withStyles';
import { capitalize } from '../utils/helpers';
import Paper from '../Paper';

export const styles = theme => {
  const backgroundColorDefault =
    theme.palette.type === 'light' ? theme.palette.grey[100] : theme.palette.grey[900];

  return {
    root: {
      display: 'flex',
      flexDirection: 'column',
      width: '100%',
      boxSizing: 'border-box', // Prevent padding issue with the Modal and fixed positioned AppBar.
      zIndex: theme.zIndex.appBar,
      flexShrink: 0,
    },
    positionFixed: {
      position: 'fixed',
      top: 0,
      left: 'auto',
      right: 0,
    },
    positionAbsolute: {
      position: 'absolute',
      top: 0,
      left: 'auto',
      right: 0,
    },
    positionStatic: {
      position: 'static',
    },
    colorDefault: {
      backgroundColor: backgroundColorDefault,
      color: theme.palette.getContrastText(backgroundColorDefault),
    },
    colorPrimary: {
      backgroundColor: theme.palette.primary.main,
      color: theme.palette.primary.contrastText,
    },
    colorSecondary: {
      backgroundColor: theme.palette.secondary.main,
      color: theme.palette.secondary.contrastText,
    },
  };
};

function AppBar(props) {
  const { children, classes, className: classNameProp, color, position, ...other } = props;

  const className = classNames(
    classes.root,
    classes[`position${capitalize(position)}`],
    {
      [classes[`color${capitalize(color)}`]]: color !== 'inherit',
      'mui-fixed': position === 'fixed', // Useful for the Dialog
    },
    classNameProp,
  );

  return (
    <Paper square component="header" elevation={4} className={className} {...other}>
      {children}
    </Paper>
  );
}

AppBar.propTypes = {
  /**
   * The content of the component.
   */
  children: PropTypes.node.isRequired,
  /**
   * Useful to extend the style applied to components.
   */
  classes: PropTypes.object.isRequired,
  /**
   * @ignore
   */
  className: PropTypes.string,
  /**
   * The color of the component. It's using the theme palette when that makes sense.
   */
  color: PropTypes.oneOf(['inherit', 'primary', 'secondary', 'default']),
  /**
   * The positioning type.
   */
  position: PropTypes.oneOf(['static', 'fixed', 'absolute']),
};

AppBar.defaultProps = {
  color: 'primary',
  position: 'fixed',
};

export default withStyles(styles, { name: 'MuiAppBar' })(AppBar);
<|endoftext|>


<commit_before>'use strict';

angular.module('csyywx')
  .controller('HomeCtrl', function ($scope, $ionicSlideBoxDelegate) {
    $scope.banners = [{
      href: '',
      src: 'assets/images/banner001.jpg',
      alt: ''
    }, {
      href: 'http://www.nonobank.com',
      src: 'assets/images/banner002.jpg',
      alt: ''
    }];
    $ionicSlideBoxDelegate.update();
    $ionicSlideBoxDelegate.start();
    console.log("update")
  })
;<commit_msg>fix slide update issue<commit_after>'use strict';

angular.module('csyywx')
  .controller('HomeCtrl', function ($scope, $ionicSlideBoxDelegate) {
    $scope.banners = [{
      href: '',
      src: 'assets/images/banner001.jpg',
      alt: ''
    }, {
      href: 'http://www.nonobank.com',
      src: 'assets/images/banner002.jpg',
      alt: ''
    }];

    $scope.$on('$ionicView.afterEnter', function(){
      console.log("------ update slide box ------");

      $ionicSlideBoxDelegate.update();
      $ionicSlideBoxDelegate.start();
    });
  })
;<|endoftext|>


<commit_before>'use strict'

const EventEmitter = require('events').EventEmitter,
      Redis        = require('ioredis'),
      logger       = require('../../util/logger')

const REDIS_EXPIRE_KEY_NOT_EXISTS_RESP = 0
const DEFAULT_REDIS_GETTER_CLIENTS_COUNT = 1

class MessagesBroker extends EventEmitter {
  constructor(connectionInfo, gettersCount) {
    super()

    this.connectionInfo = connectionInfo

    this.gettersCount = gettersCount || DEFAULT_REDIS_GETTER_CLIENTS_COUNT
    this.getters = []

    this.setter = null
    this.subscriber = null

    this.subscribed = false
  }

  createClient(name, isMainClient) {
    return new Promise(resolve => {
      const client = this[name] = new Redis(Object.assign({}, this.connectionInfo, {
        retryStrategy: times => {
          const nextReconnectionDelay = Math.min(times * 500, 5000)

          logger.info(`Redis: will try to reconnect in: ${nextReconnectionDelay / 1000} seconds`)

          return nextReconnectionDelay
        }
      }))

      //ignore redis deprecation warnings
      client.on('warning', () => undefined)
      client.on('error', error => {
        if (isMainClient) {
          logger.error(error.message)
        }
      })

      client.once('ready', () => {
        if (isMainClient) {
          client.on('connect', () => {
            logger.info('Connection with Redis has been restored')

            this.emit('reconnect')
          })
        }

        resolve()
      })
    })
  }

  forEachGetterClient(iterator) {
    for (let i = 0; i < this.gettersCount; i++) {
      iterator(i)
    }
  }

  init() {
    const getters = []

    this.forEachGetterClient(index => {
      const getterName = composeGetterClientName(index)

      getters.push(this.createClient(getterName, !this.getters.length))

      this.getters.push(this[getterName])
    })

    return Promise.all([
      ...getters,
      this.createClient('setter'),
      this.createClient('subscriber')
    ])
  }

  end() {
    const getters = []

    this.forEachGetterClient(index => {
      const getter = this[composeGetterClientName(index)]

      if (getter) {
        getters.push(getter)
      }
    })

    return Promise.all([
      ...getters,
      this.setter && this.setter.end(false),
      this.subscriber && this.subscriber.end(false)
    ])
  }

  async expireKey(key, ttl, keyDescription) {
    keyDescription = keyDescription || key

    const result = await this.setter.expire(key, ttl)

    if (result === REDIS_EXPIRE_KEY_NOT_EXISTS_RESP) {
      throw new Error(`${keyDescription} doesn't exist on server`)
    }
  }

  async getTask(tasksChannel) {
    const getter = this.getters.pop()

    let msg

    try {
      msg = await getter.blpop(tasksChannel, 0)
    } finally {
      this.getters.push(getter)
    }

    if (msg && msg.length) {
      try {
        return JSON.parse(msg[1])
      } catch (e) {
        throw new Error('Unable to parse received task. ' + e.message)
      }
    }
  }

  setTaskResult(task, result) {
    const responseChannel = task.responseChannelId

    return this.setter.publish(responseChannel, result)
  }

  subscribe(event, callback) {
    if (!this.subscribed) {
      this.subscriber.on('message', (channel, message) => {
        let parsedMessage = null

        try {
          parsedMessage = JSON.parse(message)
        } catch (e) {
          parsedMessage = message
        }

        this.emit(channel, parsedMessage)
      })

      this.subscribed = true
    }

    this.on(event, callback)

    this.subscriber.subscribe(event)
  }
}

function composeGetterClientName(index) {
  return `getter_${index}`
}

module.exports = MessagesBroker
<commit_msg>Wrap retry strategy logger to log only for main client<commit_after>'use strict'

const EventEmitter = require('events').EventEmitter,
      Redis        = require('ioredis'),
      logger       = require('../../util/logger')

const REDIS_EXPIRE_KEY_NOT_EXISTS_RESP = 0
const DEFAULT_REDIS_GETTER_CLIENTS_COUNT = 1

class MessagesBroker extends EventEmitter {
  constructor(connectionInfo, gettersCount) {
    super()

    this.connectionInfo = connectionInfo

    this.gettersCount = gettersCount || DEFAULT_REDIS_GETTER_CLIENTS_COUNT
    this.getters = []

    this.setter = null
    this.subscriber = null

    this.subscribed = false
  }

  createClient(name, isMainClient) {
    return new Promise(resolve => {
      const client = this[name] = new Redis(Object.assign({}, this.connectionInfo, {
        retryStrategy: times => {
          const nextReconnectionDelay = Math.min(times * 500, 5000)

          if (isMainClient) {
            logger.info(`Redis: will try to reconnect in: ${nextReconnectionDelay / 1000} seconds`)
          }

          return nextReconnectionDelay
        }
      }))

      //ignore redis deprecation warnings
      client.on('warning', () => undefined)
      client.on('error', error => {
        if (isMainClient) {
          logger.error(error.message)
        }
      })

      client.once('ready', () => {
        if (isMainClient) {
          client.on('connect', () => {
            logger.info('Connection with Redis has been restored')

            this.emit('reconnect')
          })
        }

        resolve()
      })
    })
  }

  forEachGetterClient(iterator) {
    for (let i = 0; i < this.gettersCount; i++) {
      iterator(i)
    }
  }

  init() {
    const getters = []

    this.forEachGetterClient(index => {
      const getterName = composeGetterClientName(index)

      getters.push(this.createClient(getterName, !this.getters.length))

      this.getters.push(this[getterName])
    })

    return Promise.all([
      ...getters,
      this.createClient('setter'),
      this.createClient('subscriber')
    ])
  }

  end() {
    const getters = []

    this.forEachGetterClient(index => {
      const getter = this[composeGetterClientName(index)]

      if (getter) {
        getters.push(getter)
      }
    })

    return Promise.all([
      ...getters,
      this.setter && this.setter.end(false),
      this.subscriber && this.subscriber.end(false)
    ])
  }

  async expireKey(key, ttl, keyDescription) {
    keyDescription = keyDescription || key

    const result = await this.setter.expire(key, ttl)

    if (result === REDIS_EXPIRE_KEY_NOT_EXISTS_RESP) {
      throw new Error(`${keyDescription} doesn't exist on server`)
    }
  }

  async getTask(tasksChannel) {
    const getter = this.getters.pop()

    let msg

    try {
      msg = await getter.blpop(tasksChannel, 0)
    } finally {
      this.getters.push(getter)
    }

    if (msg && msg.length) {
      try {
        return JSON.parse(msg[1])
      } catch (e) {
        throw new Error('Unable to parse received task. ' + e.message)
      }
    }
  }

  setTaskResult(task, result) {
    const responseChannel = task.responseChannelId

    return this.setter.publish(responseChannel, result)
  }

  subscribe(event, callback) {
    if (!this.subscribed) {
      this.subscriber.on('message', (channel, message) => {
        let parsedMessage = null

        try {
          parsedMessage = JSON.parse(message)
        } catch (e) {
          parsedMessage = message
        }

        this.emit(channel, parsedMessage)
      })

      this.subscribed = true
    }

    this.on(event, callback)

    this.subscriber.subscribe(event)
  }
}

function composeGetterClientName(index) {
  return `getter_${index}`
}

module.exports = MessagesBroker
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import settings from '../lib/settings';
import _ from 'lodash';
import queryString from 'query-string';

import { withStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import TextField from '@material-ui/core/TextField';
import GridList from '@material-ui/core/GridList';
import Toolbar from '@material-ui/core/Toolbar';
import Button from '@material-ui/core/Button';
import Select from '@material-ui/core/Select';
import Input from '@material-ui/core/Input';
import MenuItem from '@material-ui/core/MenuItem';
import LooTile from './LooTile';

import { navigate } from '@reach/router';

const styles = {
  gridRoot: {
    display: 'flex',
    flexWrap: 'wrap',
    justifyContent: 'space-around',
    overflow: 'hidden',
  },
  gridList: {
    width: '100%',
    height: 500,
  },
};

class Search extends Component {
  constructor(props) {
    super(props);

    var defaults = {
      text: '',
    };

    const parsedQuery = queryString.parse(this.props.location.search);
    this.state = {
      searching: false,
      searchParams: {
        ...defaults,
        // Apply values from query string.
        ...parsedQuery,
      },
      areas: [],
    };

    this.submit = this.submit.bind(this);
    this.doSearch = this.doSearch.bind(this);
    this.fetchAreaData = this.fetchAreaData.bind(this);
    this.updateSearchParam = this.updateSearchParam.bind(this);
    this.updateSearchField = this.updateSearchField.bind(this);
    this.updateSearchParams = this.updateSearchParams.bind(this);
  }

  componentDidMount() {
    this.doSearch(this.state.searchParams);
    this.fetchAreaData();
  }

  /**
   *
   * Performs a search given the provided query object and attaches results to state.
   *
   * @param {*} q
   */
  async doSearch(q) {
    if (!_.isEmpty(q)) {
      this.setState({ searching: true });
      const res = await fetch(
        settings.getItem('apiUrl') + '/search?' + queryString.stringify(q)
      );
      const results = await res.json();
      this.setState({ results, searching: false });
    }
  }

  /**
   * Retreives a flattened list of Areas and Area Types and attaches to state.
   */
  async fetchAreaData() {
    const searchUrl = settings.getItem('apiUrl') + '/admin_geo/areas';
    const response = await fetch(searchUrl);
    const result = await response.json();

    result.All = _.uniq(_.flatten(_.values(result))).sort();

    this.setState({
      areas: result.All,
    });
  }

  /**
   * Navigates to updated query string and submits a search to the API.
   *
   * Omits any empty search paramaters from the search.
   */
  async submit() {
    const omitEmpty = _.pickBy(this.state.searchParams);

    // If everything is empty, ensure that we at least specify the `text` param.
    if (_.isEmpty(omitEmpty)) {
      omitEmpty.text = '';
    }

    const query = queryString.stringify(omitEmpty);
    await navigate(`/search?${query}`);
    this.doSearch(omitEmpty);
  }

  /**
   *
   * Helper to update specific fields with value of event.
   *
   * @param {*} key
   * @param {*} evt
   */
  updateSearchField(key, evt) {
    this.updateSearchParam(key, evt.target.value);
  }

  /**
   *
   * Update an individual entry in the search state.
   *
   * @param {*} key
   * @param {*} val
   */
  updateSearchParam(key, val) {
    var searchParams = {
      ...this.state.searchParams,
      [key]: val,
    };

    this.setState({
      searchParams,
    });
  }

  /**
   *
   * Update multiple entries in the search state.
   *
   * @param {*} paramUpdate
   */
  updateSearchParams(paramUpdate = {}) {
    const searchParams = {
      ...this.state.searchParams,
      ...paramUpdate,
    };
    this.setState({
      searchParams,
    });
  }

  render() {
    const { classes } = this.props;
    return (

        <Card>
          <CardContent>
            <TextField
              id="text"
              label="Search in all text fields"
              helperText="eg: tescos"
              name="text"
              value={this.state.searchParams.text}
              onChange={_.partial(this.updateSearchField, 'text')}
            />

            <Select
              value={this.state.searchParams.area_name || ''}
              onChange={_.partial(this.updateSearchField, 'area_name')}
              input={<Input name="area_name" id="area-helper" />}
              displayEmpty
            >
              <MenuItem value="">All</MenuItem>
              {this.state.areas.map((item, i) => (
                <MenuItem value={item} key={i}>
                  {item}
                </MenuItem>
              ))}
            </Select>

            <Toolbar>
              <Button
                variant="contained"
                color="primary"
                onClick={this.submit}
                disabled={this.state.searching}
              >
                Search
              </Button>
            </Toolbar>
          </CardContent>
        </Card>

        {this.state.results &&
          this.state.results.docs && (

              <GridList className={classes.gridList} cellHeight={180}>
                {this.state.results.docs.map(l => {
                  return <LooTile key={l._id} loo={l} />;
                })}
              </GridList>

          )}

    );
  }
}

export default withStyles(styles)(Search);
<commit_msg>refactor: remove redundant method<commit_after>import React, { Component } from 'react';
import settings from '../lib/settings';
import _ from 'lodash';
import queryString from 'query-string';

import { withStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import TextField from '@material-ui/core/TextField';
import GridList from '@material-ui/core/GridList';
import Toolbar from '@material-ui/core/Toolbar';
import Button from '@material-ui/core/Button';
import Select from '@material-ui/core/Select';
import Input from '@material-ui/core/Input';
import MenuItem from '@material-ui/core/MenuItem';
import LooTile from './LooTile';

import { navigate } from '@reach/router';

const styles = {
  gridRoot: {
    display: 'flex',
    flexWrap: 'wrap',
    justifyContent: 'space-around',
    overflow: 'hidden',
  },
  gridList: {
    width: '100%',
    height: 500,
  },
};

class Search extends Component {
  constructor(props) {
    super(props);

    var defaults = {
      text: '',
    };

    const parsedQuery = queryString.parse(this.props.location.search);
    this.state = {
      searching: false,
      searchParams: {
        ...defaults,
        // Apply values from query string.
        ...parsedQuery,
      },
      areas: [],
    };

    this.submit = this.submit.bind(this);
    this.doSearch = this.doSearch.bind(this);
    this.fetchAreaData = this.fetchAreaData.bind(this);
    this.updateSearchParam = this.updateSearchParam.bind(this);
    this.updateSearchField = this.updateSearchField.bind(this);
  }

  componentDidMount() {
    this.doSearch(this.state.searchParams);
    this.fetchAreaData();
  }

  /**
   *
   * Performs a search given the provided query object and attaches results to state.
   *
   * @param {*} q
   */
  async doSearch(q) {
    if (!_.isEmpty(q)) {
      this.setState({ searching: true });
      const res = await fetch(
        settings.getItem('apiUrl') + '/search?' + queryString.stringify(q)
      );
      const results = await res.json();
      this.setState({ results, searching: false });
    }
  }

  /**
   * Retreives a flattened list of Areas and Area Types and attaches to state.
   */
  async fetchAreaData() {
    const searchUrl = settings.getItem('apiUrl') + '/admin_geo/areas';
    const response = await fetch(searchUrl);
    const result = await response.json();

    result.All = _.uniq(_.flatten(_.values(result))).sort();

    this.setState({
      areas: result.All,
    });
  }

  /**
   * Navigates to updated query string and submits a search to the API.
   *
   * Omits any empty search paramaters from the search.
   */
  async submit() {
    const omitEmpty = _.pickBy(this.state.searchParams);

    // If everything is empty, ensure that we at least specify the `text` param.
    if (_.isEmpty(omitEmpty)) {
      omitEmpty.text = '';
    }

    const query = queryString.stringify(omitEmpty);
    await navigate(`/search?${query}`);
    this.doSearch(omitEmpty);
  }

  /**
   *
   * Helper to update specific fields with value of event.
   *
   * @param {*} key
   * @param {*} evt
   */
  updateSearchField(key, evt) {
    this.updateSearchParam(key, evt.target.value);
  }

  /**
   *
   * Update an individual entry in the search state.
   *
   * @param {*} key
   * @param {*} val
   */
  updateSearchParam(key, val) {
    var searchParams = {
      ...this.state.searchParams,
      [key]: val,
    };

    this.setState({
      searchParams,
    });
  }

  render() {
    const { classes } = this.props;
    return (

        <Card>
          <CardContent>
            <TextField
              id="text"
              label="Search in all text fields"
              helperText="eg: tescos"
              name="text"
              value={this.state.searchParams.text}
              onChange={_.partial(this.updateSearchField, 'text')}
            />

            <Select
              value={this.state.searchParams.area_name || ''}
              onChange={_.partial(this.updateSearchField, 'area_name')}
              input={<Input name="area_name" id="area-helper" />}
              displayEmpty
            >
              <MenuItem value="">All</MenuItem>
              {this.state.areas.map((item, i) => (
                <MenuItem value={item} key={i}>
                  {item}
                </MenuItem>
              ))}
            </Select>

            <Toolbar>
              <Button
                variant="contained"
                color="primary"
                onClick={this.submit}
                disabled={this.state.searching}
              >
                Search
              </Button>
            </Toolbar>
          </CardContent>
        </Card>

        {this.state.results &&
          this.state.results.docs && (

              <GridList className={classes.gridList} cellHeight={180}>
                {this.state.results.docs.map(l => {
                  return <LooTile key={l._id} loo={l} />;
                })}
              </GridList>

          )}

    );
  }
}

export default withStyles(styles)(Search);
<|endoftext|>


<commit_before>process.env.NODE_ENV = 'development';

// Load environment variables from .env file. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set.
// https://github.com/motdotla/dotenv
require('dotenv').config({silent: true});

var chalk = require('chalk');
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var historyApiFallback = require('connect-history-api-fallback');
var httpProxyMiddleware = require('http-proxy-middleware');
var detect = require('detect-port');
var clearConsole = require('react-dev-utils/clearConsole');
var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
var formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
var getProcessForPort = require('react-dev-utils/getProcessForPort');
var openBrowser = require('react-dev-utils/openBrowser');
var prompt = require('react-dev-utils/prompt');
var pathExists = require('path-exists');
var config = require('../config/webpack.config.dev');
var paths = require('../config/paths');

var useYarn = pathExists.sync(paths.yarnLockFile);
var cli = useYarn ? 'yarn' : 'npm';
var isInteractive = process.stdout.isTTY;

// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
  process.exit(1);
}

// Tools like Cloud9 rely on this.
var DEFAULT_PORT = process.env.PORT || 3000;
var compiler;
var handleCompile;

// You can safely remove this after ejecting.
// We only use this block for testing of Create React App itself:
var isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1);
if (isSmokeTest) {
  handleCompile = function (err, stats) {
    if (err || stats.hasErrors() || stats.hasWarnings()) {
      process.exit(1);
    } else {
      process.exit(0);
    }
  };
}

function setupCompiler(host, port, protocol) {
  // "Compiler" is a low-level interface to Webpack.
  // It lets us listen to some events and provide our own custom messages.
  compiler = webpack(config, handleCompile);

  // "invalid" event fires when you have changed a file, and Webpack is
  // recompiling a bundle. WebpackDevServer takes care to pause serving the
  // bundle, so if you refresh, it'll wait instead of serving the old one.
  // "invalid" is short for "bundle invalidated", it doesn't imply any errors.
  compiler.plugin('invalid', function() {
    if (isInteractive) {
      clearConsole();
    }
    console.log('Compiling...');
  });

  var isFirstCompile = true;

  // "done" event fires when Webpack has finished recompiling the bundle.
  // Whether or not you have warnings or errors, you will get this event.
  compiler.plugin('done', function(stats) {
    if (isInteractive) {
      clearConsole();
    }

    // We have switched off the default Webpack output in WebpackDevServer
    // options so we are going to "massage" the warnings and errors and present
    // them in a readable focused way.
    var messages = formatWebpackMessages(stats.toJson({}, true));
    var isSuccessful = !messages.errors.length && !messages.warnings.length;
    var showInstructions = isSuccessful && (isInteractive || isFirstCompile);

    if (isSuccessful) {
      console.log(chalk.green('Compiled successfully!'));
    }

    if (showInstructions) {
      console.log();
      console.log('The app is running at:');
      console.log();
      console.log('  ' + chalk.cyan(protocol + '://' + host + ':' + port + '/'));
      console.log();
      console.log('Note that the development build is not optimized.');
      console.log('To create a production build, use ' + chalk.cyan(cli + ' run build') + '.');
      console.log();
      isFirstCompile = false;
    }

    // If errors exist, only show errors.
    if (messages.errors.length) {
      console.log(chalk.red('Failed to compile.'));
      console.log();
      messages.errors.forEach(message => {
        console.log(message);
        console.log();
      });
      return;
    }

    // Show warnings if no errors were found.
    if (messages.warnings.length) {
      console.log(chalk.yellow('Compiled with warnings.'));
      console.log();
      messages.warnings.forEach(message => {
        console.log(message);
        console.log();
      });
      // Teach some ESLint tricks.
      console.log('You may use special comments to disable some warnings.');
      console.log('Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.');
      console.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.');
    }
  });
}

// We need to provide a custom onError function for httpProxyMiddleware.
// It allows us to log custom error messages on the console.
function onProxyError(proxy) {
  return function(err, req, res){
    var host = req.headers && req.headers.host;
    console.log(
      chalk.red('Proxy error:') + ' Could not proxy request ' + chalk.cyan(req.url) +
      ' from ' + chalk.cyan(host) + ' to ' + chalk.cyan(proxy) + '.'
    );
    console.log(
      'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' +
      chalk.cyan(err.code) + ').'
    );
    console.log();

    // And immediately send the proper error response to the client.
    // Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side.
    if (res.writeHead && !res.headersSent) {
        res.writeHead(500);
    }
    res.end('Proxy error: Could not proxy request ' + req.url + ' from ' +
      host + ' to ' + proxy + ' (' + err.code + ').'
    );
  }
}

function addMiddleware(devServer) {
  // `proxy` lets you to specify a fallback server during development.
  // Every unrecognized request will be forwarded to it.
  var proxy = require(paths.appPackageJson).proxy;
  devServer.use(historyApiFallback({
    // Paths with dots should still use the history fallback.
    // See https://github.com/facebookincubator/create-react-app/issues/387.
    disableDotRule: true,
    // For single page apps, we generally want to fallback to /index.html.
    // However we also want to respect `proxy` for API calls.
    // So if `proxy` is specified, we need to decide which fallback to use.
    // We use a heuristic: if request `accept`s text/html, we pick /index.html.
    // Modern browsers include text/html into `accept` header when navigating.
    // However API calls like `fetch()` won’t generally accept text/html.
    // If this heuristic doesn’t work well for you, don’t use `proxy`.
    htmlAcceptHeaders: proxy ?
      ['text/html'] :
      ['text/html', '*/*']
  }));
  if (proxy) {
    if (typeof proxy !== 'string') {
      console.log(chalk.red('When specified, "proxy" in package.json must be a string.'));
      console.log(chalk.red('Instead, the type of "proxy" was "' + typeof proxy + '".'));
      console.log(chalk.red('Either remove "proxy" from package.json, or make it a string.'));
      process.exit(1);
    }

    // Otherwise, if proxy is specified, we will let it handle any request.
    // There are a few exceptions which we won't send to the proxy:
    // - /index.html (served as HTML5 history API fallback)
    // - /*.hot-update.json (WebpackDevServer uses this too for hot reloading)
    // - /sockjs-node/* (WebpackDevServer uses this for hot reloading)
    // Tip: use https://jex.im/regulex/ to visualize the regex
    var mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/;

    // Pass the scope regex both to Express and to the middleware for proxying
    // of both HTTP and WebSockets to work without false positives.
    var hpm = httpProxyMiddleware(pathname => mayProxy.test(pathname), {
      target: proxy,
      logLevel: 'silent',
      onProxyReq: function(proxyReq, req, res) {
        // Browers may send Origin headers even with same-origin
        // requests. To prevent CORS issues, we have to change
        // the Origin to match the target URL.
        if (proxyReq.getHeader('origin')) {
          proxyReq.setHeader('origin', proxy);
        }
      },
      onError: onProxyError(proxy),
      secure: false,
      changeOrigin: true,
      ws: true
    });
    devServer.use(mayProxy, hpm);

    // Listen for the websocket 'upgrade' event and upgrade the connection.
    // If this is not done, httpProxyMiddleware will not try to upgrade until
    // an initial plain HTTP request is made.
    devServer.listeningApp.on('upgrade', hpm.upgrade);
  }

  // Finally, by now we have certainly resolved the URL.
  // It may be /index.html, so let the dev server try serving it again.
  devServer.use(devServer.middleware);
}

function runDevServer(host, port, protocol) {
  var devServer = new WebpackDevServer(compiler, {
    // Enable gzip compression of generated files.
    compress: true,
    // Silence WebpackDevServer's own logs since they're generally not useful.
    // It will still show compile warnings and errors with this setting.
    clientLogLevel: 'none',
    // By default WebpackDevServer serves physical files from current directory
    // in addition to all the virtual build products that it serves from memory.
    // This is confusing because those files won’t automatically be available in
    // production build folder unless we copy them. However, copying the whole
    // project directory is dangerous because we may expose sensitive files.
    // Instead, we establish a convention that only files in `public` directory
    // get served. Our build script will copy `public` into the `build` folder.
    // In `index.html`, you can get URL of `public` folder with %PUBLIC_PATH%:
    // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    // In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
    // Note that we only recommend to use `public` folder as an escape hatch
    // for files like `favicon.ico`, `manifest.json`, and libraries that are
    // for some reason broken when imported through Webpack. If you just want to
    // use an image, put it in `src` and `import` it from JavaScript instead.
    contentBase: paths.appPublic,
    // Enable hot reloading server. It will provide /sockjs-node/ endpoint
    // for the WebpackDevServer client so it can learn when the files were
    // updated. The WebpackDevServer client is included as an entry point
    // in the Webpack development configuration. Note that only changes
    // to CSS are currently hot reloaded. JS changes will refresh the browser.
    hot: true,
    // It is important to tell WebpackDevServer to use the same "root" path
    // as we specified in the config. In development, we always serve from /.
    publicPath: config.output.publicPath,
    // WebpackDevServer is noisy by default so we emit custom message instead
    // by listening to the compiler events with `compiler.plugin` calls above.
    quiet: true,
    // Reportedly, this avoids CPU overload on some systems.
    // https://github.com/facebookincubator/create-react-app/issues/293
    watchOptions: {
      ignored: /node_modules/
    },
    // Enable HTTPS if the HTTPS environment variable is set to 'true'
    https: protocol === "https",
    host: host
  });

  // Our custom middleware proxies requests to /index.html or a remote API.
  addMiddleware(devServer);

  // Launch WebpackDevServer.
  devServer.listen(port, (err, result) => {
    if (err) {
      return console.log(err);
    }

    if (isInteractive) {
      clearConsole();
    }
    console.log(chalk.cyan('Starting the development server...'));
    console.log();

    if (isInteractive) {
      openBrowser(protocol + '://' + host + ':' + port + '/');
    }
  });
}

function run(port) {
  var protocol = process.env.HTTPS === 'true' ? "https" : "http";
  var host = process.env.HOST || 'localhost';
  setupCompiler(host, port, protocol);
  runDevServer(host, port, protocol);
}

// We attempt to use the default port but if it is busy, we offer the user to
// run on a different port. `detect()` Promise resolves to the next free port.
detect(DEFAULT_PORT).then(port => {
  if (port === DEFAULT_PORT) {
    run(port);
    return;
  }

  if (isInteractive) {
    clearConsole();
    var existingProcess = getProcessForPort(DEFAULT_PORT);
    var question =
      chalk.yellow('Something is already running on port ' + DEFAULT_PORT + '.' +
        ((existingProcess) ? ' Probably:\n  ' + existingProcess : '')) +
        '\n\nWould you like to run the app on another port instead?';

    prompt(question, true).then(shouldChangePort => {
      if (shouldChangePort) {
        run(port);
      }
    });
  } else {
    console.log(chalk.red('Something is already running on port ' + DEFAULT_PORT + '.'));
  }
});
<commit_msg>Better local url<commit_after>process.env.NODE_ENV = 'development';

// Load environment variables from .env file. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set.
// https://github.com/motdotla/dotenv
require('dotenv').config({silent: true});

var chalk = require('chalk');
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var historyApiFallback = require('connect-history-api-fallback');
var httpProxyMiddleware = require('http-proxy-middleware');
var detect = require('detect-port');
var clearConsole = require('react-dev-utils/clearConsole');
var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
var formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
var getProcessForPort = require('react-dev-utils/getProcessForPort');
var openBrowser = require('react-dev-utils/openBrowser');
var prompt = require('react-dev-utils/prompt');
var pathExists = require('path-exists');
var config = require('../config/webpack.config.dev');
var paths = require('../config/paths');

var useYarn = pathExists.sync(paths.yarnLockFile);
var cli = useYarn ? 'yarn' : 'npm';
var isInteractive = process.stdout.isTTY;

// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
  process.exit(1);
}

// Tools like Cloud9 rely on this.
var DEFAULT_PORT = process.env.PORT || 3000;
var compiler;
var handleCompile;

// You can safely remove this after ejecting.
// We only use this block for testing of Create React App itself:
var isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1);
if (isSmokeTest) {
  handleCompile = function (err, stats) {
    if (err || stats.hasErrors() || stats.hasWarnings()) {
      process.exit(1);
    } else {
      process.exit(0);
    }
  };
}

function setupCompiler(host, port, protocol) {
  // "Compiler" is a low-level interface to Webpack.
  // It lets us listen to some events and provide our own custom messages.
  compiler = webpack(config, handleCompile);

  // "invalid" event fires when you have changed a file, and Webpack is
  // recompiling a bundle. WebpackDevServer takes care to pause serving the
  // bundle, so if you refresh, it'll wait instead of serving the old one.
  // "invalid" is short for "bundle invalidated", it doesn't imply any errors.
  compiler.plugin('invalid', function() {
    if (isInteractive) {
      clearConsole();
    }
    console.log('Compiling...');
  });

  var isFirstCompile = true;

  // "done" event fires when Webpack has finished recompiling the bundle.
  // Whether or not you have warnings or errors, you will get this event.
  compiler.plugin('done', function(stats) {
    if (isInteractive) {
      clearConsole();
    }

    // We have switched off the default Webpack output in WebpackDevServer
    // options so we are going to "massage" the warnings and errors and present
    // them in a readable focused way.
    var messages = formatWebpackMessages(stats.toJson({}, true));
    var isSuccessful = !messages.errors.length && !messages.warnings.length;
    var showInstructions = isSuccessful && (isInteractive || isFirstCompile);

    if (isSuccessful) {
      console.log(chalk.green('Compiled successfully!'));
    }

    if (showInstructions) {
      console.log();
      console.log('The app is running at:');
      console.log();
      console.log('  ' + chalk.cyan(protocol + '://' + host + ':' + port + '/'));
      console.log();
      console.log('Note that the development build is not optimized.');
      console.log('To create a production build, use ' + chalk.cyan(cli + ' run build') + '.');
      console.log();
      isFirstCompile = false;
    }

    // If errors exist, only show errors.
    if (messages.errors.length) {
      console.log(chalk.red('Failed to compile.'));
      console.log();
      messages.errors.forEach(message => {
        console.log(message);
        console.log();
      });
      return;
    }

    // Show warnings if no errors were found.
    if (messages.warnings.length) {
      console.log(chalk.yellow('Compiled with warnings.'));
      console.log();
      messages.warnings.forEach(message => {
        console.log(message);
        console.log();
      });
      // Teach some ESLint tricks.
      console.log('You may use special comments to disable some warnings.');
      console.log('Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.');
      console.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.');
    }
  });
}

// We need to provide a custom onError function for httpProxyMiddleware.
// It allows us to log custom error messages on the console.
function onProxyError(proxy) {
  return function(err, req, res){
    var host = req.headers && req.headers.host;
    console.log(
      chalk.red('Proxy error:') + ' Could not proxy request ' + chalk.cyan(req.url) +
      ' from ' + chalk.cyan(host) + ' to ' + chalk.cyan(proxy) + '.'
    );
    console.log(
      'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' +
      chalk.cyan(err.code) + ').'
    );
    console.log();

    // And immediately send the proper error response to the client.
    // Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side.
    if (res.writeHead && !res.headersSent) {
        res.writeHead(500);
    }
    res.end('Proxy error: Could not proxy request ' + req.url + ' from ' +
      host + ' to ' + proxy + ' (' + err.code + ').'
    );
  }
}

function addMiddleware(devServer) {
  // `proxy` lets you to specify a fallback server during development.
  // Every unrecognized request will be forwarded to it.
  var proxy = require(paths.appPackageJson).proxy;
  devServer.use(historyApiFallback({
    // Paths with dots should still use the history fallback.
    // See https://github.com/facebookincubator/create-react-app/issues/387.
    disableDotRule: true,
    // For single page apps, we generally want to fallback to /index.html.
    // However we also want to respect `proxy` for API calls.
    // So if `proxy` is specified, we need to decide which fallback to use.
    // We use a heuristic: if request `accept`s text/html, we pick /index.html.
    // Modern browsers include text/html into `accept` header when navigating.
    // However API calls like `fetch()` won’t generally accept text/html.
    // If this heuristic doesn’t work well for you, don’t use `proxy`.
    htmlAcceptHeaders: proxy ?
      ['text/html'] :
      ['text/html', '*/*']
  }));
  if (proxy) {
    if (typeof proxy !== 'string') {
      console.log(chalk.red('When specified, "proxy" in package.json must be a string.'));
      console.log(chalk.red('Instead, the type of "proxy" was "' + typeof proxy + '".'));
      console.log(chalk.red('Either remove "proxy" from package.json, or make it a string.'));
      process.exit(1);
    }

    // Otherwise, if proxy is specified, we will let it handle any request.
    // There are a few exceptions which we won't send to the proxy:
    // - /index.html (served as HTML5 history API fallback)
    // - /*.hot-update.json (WebpackDevServer uses this too for hot reloading)
    // - /sockjs-node/* (WebpackDevServer uses this for hot reloading)
    // Tip: use https://jex.im/regulex/ to visualize the regex
    var mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/;

    // Pass the scope regex both to Express and to the middleware for proxying
    // of both HTTP and WebSockets to work without false positives.
    var hpm = httpProxyMiddleware(pathname => mayProxy.test(pathname), {
      target: proxy,
      logLevel: 'silent',
      onProxyReq: function(proxyReq, req, res) {
        // Browers may send Origin headers even with same-origin
        // requests. To prevent CORS issues, we have to change
        // the Origin to match the target URL.
        if (proxyReq.getHeader('origin')) {
          proxyReq.setHeader('origin', proxy);
        }
      },
      onError: onProxyError(proxy),
      secure: false,
      changeOrigin: true,
      ws: true
    });
    devServer.use(mayProxy, hpm);

    // Listen for the websocket 'upgrade' event and upgrade the connection.
    // If this is not done, httpProxyMiddleware will not try to upgrade until
    // an initial plain HTTP request is made.
    devServer.listeningApp.on('upgrade', hpm.upgrade);
  }

  // Finally, by now we have certainly resolved the URL.
  // It may be /index.html, so let the dev server try serving it again.
  devServer.use(devServer.middleware);
}

function runDevServer(host, port, protocol) {
  var devServer = new WebpackDevServer(compiler, {
    // Enable gzip compression of generated files.
    compress: true,
    // Silence WebpackDevServer's own logs since they're generally not useful.
    // It will still show compile warnings and errors with this setting.
    clientLogLevel: 'none',
    // By default WebpackDevServer serves physical files from current directory
    // in addition to all the virtual build products that it serves from memory.
    // This is confusing because those files won’t automatically be available in
    // production build folder unless we copy them. However, copying the whole
    // project directory is dangerous because we may expose sensitive files.
    // Instead, we establish a convention that only files in `public` directory
    // get served. Our build script will copy `public` into the `build` folder.
    // In `index.html`, you can get URL of `public` folder with %PUBLIC_PATH%:
    // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    // In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
    // Note that we only recommend to use `public` folder as an escape hatch
    // for files like `favicon.ico`, `manifest.json`, and libraries that are
    // for some reason broken when imported through Webpack. If you just want to
    // use an image, put it in `src` and `import` it from JavaScript instead.
    contentBase: paths.appPublic,
    // Enable hot reloading server. It will provide /sockjs-node/ endpoint
    // for the WebpackDevServer client so it can learn when the files were
    // updated. The WebpackDevServer client is included as an entry point
    // in the Webpack development configuration. Note that only changes
    // to CSS are currently hot reloaded. JS changes will refresh the browser.
    hot: true,
    // It is important to tell WebpackDevServer to use the same "root" path
    // as we specified in the config. In development, we always serve from /.
    publicPath: config.output.publicPath,
    // WebpackDevServer is noisy by default so we emit custom message instead
    // by listening to the compiler events with `compiler.plugin` calls above.
    quiet: true,
    // Reportedly, this avoids CPU overload on some systems.
    // https://github.com/facebookincubator/create-react-app/issues/293
    watchOptions: {
      ignored: /node_modules/
    },
    // Enable HTTPS if the HTTPS environment variable is set to 'true'
    https: protocol === "https",
    host: host
  });

  // Our custom middleware proxies requests to /index.html or a remote API.
  addMiddleware(devServer);

  // Launch WebpackDevServer.
  devServer.listen(port, (err, result) => {
    if (err) {
      return console.log(err);
    }

    if (isInteractive) {
      clearConsole();
    }
    console.log(chalk.cyan('Starting the development server...'));
    console.log();

    if (isInteractive) {
      openBrowser(protocol + '://' + host + ':' + port + '/');
    }
  });
}

function run(port) {
  var protocol = process.env.HTTPS === 'true' ? "https" : "http";
  var host = process.env.HOST || 'local.amodahl.no';
  setupCompiler(host, port, protocol);
  runDevServer(host, port, protocol);
}

// We attempt to use the default port but if it is busy, we offer the user to
// run on a different port. `detect()` Promise resolves to the next free port.
detect(DEFAULT_PORT).then(port => {
  if (port === DEFAULT_PORT) {
    run(port);
    return;
  }

  if (isInteractive) {
    clearConsole();
    var existingProcess = getProcessForPort(DEFAULT_PORT);
    var question =
      chalk.yellow('Something is already running on port ' + DEFAULT_PORT + '.' +
        ((existingProcess) ? ' Probably:\n  ' + existingProcess : '')) +
        '\n\nWould you like to run the app on another port instead?';

    prompt(question, true).then(shouldChangePort => {
      if (shouldChangePort) {
        run(port);
      }
    });
  } else {
    console.log(chalk.red('Something is already running on port ' + DEFAULT_PORT + '.'));
  }
});
<|endoftext|>


<commit_before>//
// Copyright (c) 2011 Frank Kohlhepp
// https://github.com/frankkohlhepp/fancy-settings
// License: LGPL v2.1
//
(function () {
    var Bundle = new Class({
        "creator": null,
        "active": false,
        "tab": null,
        "content": null,

        "initialize": function (creator) {
            this.creator = creator;

            // Create DOM elements
            this.tab = new Element("div", {
                "class": "tab"
            });

            this.content = new Element("div", {
                "class": "tab-content"
            });

            // Create event handlers
            this.tab.addEvent("click", (function (event) {
                this.activate();
            }).bind(this));
        },

        "activate": function () {
            // Check if we're already active
            if (this.active) {
                return;
            }

            // Deactivate the currently active bundle
            if (this.creator.activeBundle) {
                this.creator.activeBundle.deactivate();
            }

            // Activate us
            this.tab.addClass("selected");
            this.content.addClass("show");

            // Tell our creator that we're the active bundle
            this.creator.activeBundle = this;

            this.active = true;
        },

        "deactivate": function () {
            // Check if we're already unactive
            if (!this.active) {
                return;
            }

            // Deactivate us
            this.tab.removeClass("selected");
            this.content.removeClass("show");

            this.active = false;
        }
    });

    this.Tab = new Class({
        "activeBundle": null,

        "initialize": function (tabContainer, contentContainer) {
            // Check containers
            if (typeOf(tabContainer) !== "element" || typeOf(contentContainer) !== "element") {
                throw "containerNotAnElement";
            }

            this.tabContainer = tabContainer;
            this.contentContainer = contentContainer;
        },

        "create": function () {
            // Create a new bundle
            var bundle = new Bundle(this);

            // Inject the bundle into the DOM
            bundle.tab.inject(this.tabContainer);
            bundle.content.inject(this.contentContainer);

            // Activate the bundle if it's the first created
            if (!this.activeBundle) {
                bundle.activate();
            }

            return bundle;
        }
    });
}());
<commit_msg>Optimized Tab Class<commit_after>//
// Copyright (c) 2011 Frank Kohlhepp
// https://github.com/frankkohlhepp/fancy-settings
// License: LGPL v2.1
//
(function () {
    var Bundle,
        Tab;

    Bundle = new Class({
        "initialize": function (creator) {
            this.creator = creator;

            // Create DOM elements
            this.tab = new Element("div", {
                "class": "tab"
            });

            this.content = new Element("div", {
                "class": "tab-content"
            });

            // Create event handlers
            this.tab.addEvent("click", (function (event) {
                this.activate();
            }).bind(this));
        },

        "activate": function () {
            if (this.creator.activeBundle && this.creator.activeBundle !== this) {
                this.creator.activeBundle.deactivate();
            }
            this.tab.addClass("selected");
            this.content.addClass("show");
            this.creator.activeBundle = this;
        },

        "deactivate": function () {
            this.tab.removeClass("selected");
            this.content.removeClass("show");
            this.creator.activeBundle = null;
        }
    });

    Tab = this.Tab = new Class({
        "activeBundle": null,

        "initialize": function (tabContainer, contentContainer) {
            this.tabContainer = tabContainer;
            this.contentContainer = contentContainer;
        },

        "create": function () {
            // Create a new bundle
            var bundle = new Bundle(this);

            // Inject the bundle into the DOM
            bundle.tab.inject(this.tabContainer);
            bundle.content.inject(this.contentContainer);

            // Activate the bundle if it's the first created
            if (!this.activeBundle) {
                bundle.activate();
            }

            return bundle;
        }
    });
}());
<|endoftext|>


<commit_before>import jsdom from 'mocha-jsdom';
import chai, { assert, expect } from 'chai';
import jsxr from '../src/jsxr';

describe('jsxr', () => {
  // Load browser API
  jsdom();

  it('should return an HTMLElement with attributes', () => {
    const Test = (

    );

    expect(Test).to.be.an.instanceof(HTMLElement);
    assert.equal(Test.getAttribute('data-x'), 'x');
  });

  it('should accept custom components', () => {
    const Letters = (

        {['A', 'B', 'C'].map((item, idx) => {
          return
{ item }
;
        })}

    );

    const Container = (

        <Letters className='letters-list' />

    );

    const lettersList = Container.querySelector('.letters-list');
    const firstItemIdx = lettersList.querySelector('li').getAttribute('data-item-idx');

    expect(lettersList).to.be.an.instanceof(HTMLElement);
    assert.equal(parseInt(firstItemIdx, 10), 0);
    assert.equal(lettersList.children.length, 3);
  });

  it('should should inline styles', () => {
    const rules = {
      backgroundColor: '#fff',
      marginBottom: '20px',
    };

    const Test =

;

    assert.equal(Test.style.backgroundColor, '#fff');
    assert.equal(Test.style.marginBottom, '20px');
  });
});
<commit_msg>check color vals against native rgb vals<commit_after>import jsdom from 'mocha-jsdom';
import chai, { assert, expect } from 'chai';
import jsxr from '../src/jsxr';

describe('jsxr', () => {
  // Load browser API
  jsdom();

  it('should return an HTMLElement with attributes', () => {
    const Test = (

    );

    expect(Test).to.be.an.instanceof(HTMLElement);
    assert.equal(Test.getAttribute('data-x'), 'x');
  });

  it('should accept custom components', () => {
    const Letters = (

        {['A', 'B', 'C'].map((item, idx) => {
          return
{ item }
;
        })}

    );

    const Container = (

        <Letters className='letters-list' />

    );

    const lettersList = Container.querySelector('.letters-list');
    const firstItemIdx = lettersList.querySelector('li').getAttribute('data-item-idx');

    expect(lettersList).to.be.an.instanceof(HTMLElement);
    assert.equal(parseInt(firstItemIdx, 10), 0);
    assert.equal(lettersList.children.length, 3);
  });

  it('should should inline styles', () => {
    const rules = {
      backgroundColor: '#fff',
      marginBottom: '20px',
    };

    const Test =

;

    assert.equal(Test.style.backgroundColor, 'rgb(255, 255, 255)');
    assert.equal(Test.style.marginBottom, '20px');
  });
});
<|endoftext|>