<commit_before>/*
 * @author     Alexander Shumilov
 * @copyright  2013-2019 MapCentia ApS
 * @license    http://www.gnu.org/licenses/#AGPL  GNU AFFERO GENERAL PUBLIC LICENSE 3
 */

/**
 * Returning PNG image with a Vidi state snapshot screenshot
 */

require('dotenv').config();

var express = require('express');
var router = express.Router();
var headless = require('./headlessBrowserPool').pool;

const returnPNGForStateSnapshot = (localRequest, localResponse) => {
    let go = false;
    let errorMessages = [];
    if (!localRequest.params.db) errorMessages.push(`database is not defined`);
    if (!localRequest.query.state && !localRequest.query.filter) errorMessages.push(`state or filter paramter is not defined`);
    if (!localRequest.headers.host) errorMessages.push(`"Host" header has to be correctly passed to the app`);

    let width = (localRequest.query.width && parseInt(localRequest.query.width) > 0 ? parseInt(localRequest.query.width) : 800);
    let height = (localRequest.query.height && parseInt(localRequest.query.height) > 0 ? parseInt(localRequest.query.height) : 600);
    let config = (localRequest.query.config ? `&config=${localRequest.query.config}` : ``);

    let state = (localRequest.query.state ? `&state=${localRequest.query.state}` : ``);
    let filter = (localRequest.query.filter ? `&initialFilter=${localRequest.query.filter}` : ``);

    if (errorMessages.length === 0) {
        const port = process.env.PORT ? process.env.PORT : 3000;
        let url = `http://127.0.0.1:${port}/app/${localRequest.params.db}/${localRequest.params.scheme}/?tmpl=blank.tmpl${state}${filter}${config}`;
        headless.acquire().then(browser => {
            browser.newPage().then(page => {
                page.emulate({
                    viewport: { width, height },
                    userAgent: 'Puppeteer'
                }).then(() => {
                    page.on('console', msg => {
                        console.log(msg.text());

                        if (msg.text().indexOf(`No active layers in snapshot`) !== -1) { // Print as soon Vidi is loaded
                            go = true;
                        }
                        if (msg.text().indexOf(`Active layers in snapshot`) !== -1) { // Wait until layers from snapshot is loaded
                            go = false; // Wait for overlays to load
                        }

                        if (localRequest.query.filter) { // Print as soon Vidi is loaded
                            go = true;
                        }

                        if (
                            // Print as soon Vidi is done loading
                            (msg.text().indexOf(`Vidi is now loaded`) !== -1 && go) ||
                            // Wait until all overlays from snapshot is loaded
                            (msg.text().indexOf(`Layers all loaded`) !== -1 && !go)
                        ) {
                            console.log('App was loaded, generating PNG');
                            setTimeout(() => {
                                page.evaluate(`$('.leaflet-top').remove();$('#loadscreen').remove();`).then(() => {
                                    page.screenshot({
                                        encoding: `base64`
                                    }).then(data => {
                                        let img = new Buffer.from(data, 'base64');
                                        localResponse.writeHead(200, {
                                            'Content-Type': 'image/png',
                                            'Content-Length': img.length
                                        });
                                        headless.destroy(browser);
                                        localResponse.end(img);
                                    }).catch(error => {
                                        localResponse.status(500);
                                        localResponse.send(error);
                                    });
                                }).catch(error => {
                                    localResponse.status(500);
                                    localResponse.send(error);
                                });
                            }, 200);
                        }
                    });

                    page.goto(url);
                }).catch(error => {
                    localResponse.status(500);
                    localResponse.send(error);
                });
            });
        }).catch(error => {
            console.error(error);
            localResponse.status(500);
            localResponse.send(`Error occured: ${error}`);
        });
    } else {
        localResponse.status(400);
        localResponse.send(`Errors occured: ${errorMessages.join(`,`)}`);
    }
};

router.get('/api/static/:db/:scheme?', returnPNGForStateSnapshot);
router.get('/api/static/:db/:scheme?/', returnPNGForStateSnapshot);

module.exports = router;
<commit_msg>Put a bit longer delay when zooming to feature<commit_after>/*
 * @author     Alexander Shumilov
 * @copyright  2013-2019 MapCentia ApS
 * @license    http://www.gnu.org/licenses/#AGPL  GNU AFFERO GENERAL PUBLIC LICENSE 3
 */

/**
 * Returning PNG image with a Vidi state snapshot screenshot
 */

require('dotenv').config();

var express = require('express');
var router = express.Router();
var headless = require('./headlessBrowserPool').pool;

const returnPNGForStateSnapshot = (localRequest, localResponse) => {
    let go = false;
    let errorMessages = [];
    if (!localRequest.params.db) errorMessages.push(`database is not defined`);
    if (!localRequest.query.state && !localRequest.query.filter) errorMessages.push(`state or filter paramter is not defined`);
    if (!localRequest.headers.host) errorMessages.push(`"Host" header has to be correctly passed to the app`);

    let width = (localRequest.query.width && parseInt(localRequest.query.width) > 0 ? parseInt(localRequest.query.width) : 800);
    let height = (localRequest.query.height && parseInt(localRequest.query.height) > 0 ? parseInt(localRequest.query.height) : 600);
    let config = (localRequest.query.config ? `&config=${localRequest.query.config}` : ``);

    let state = (localRequest.query.state ? `&state=${localRequest.query.state}` : ``);
    let filter = (localRequest.query.filter ? `&initialFilter=${localRequest.query.filter}` : ``);
    let delay = 200;

    if (errorMessages.length === 0) {
        const port = process.env.PORT ? process.env.PORT : 3000;
        let url = `http://127.0.0.1:${port}/app/${localRequest.params.db}/${localRequest.params.scheme}/?tmpl=blank.tmpl${state}${filter}${config}`;
        headless.acquire().then(browser => {
            browser.newPage().then(page => {
                page.emulate({
                    viewport: { width, height },
                    userAgent: 'Puppeteer'
                }).then(() => {
                    page.on('console', msg => {
                        console.log(msg.text());

                        if (msg.text().indexOf(`No active layers in snapshot`) !== -1) { // Print as soon Vidi is loaded
                            go = true;
                        }
                        if (msg.text().indexOf(`Active layers in snapshot`) !== -1) { // Wait until layers from snapshot is loaded
                            go = false; // Wait for overlays to load
                        }

                        if (localRequest.query.filter) { // Print as soon Vidi is loaded
                            go = true;
                            delay = 1000; // wait a bit longer because zooming has to finish
                        }

                        if (
                            // Print as soon Vidi is done loading
                            (msg.text().indexOf(`Vidi is now loaded`) !== -1 && go) ||
                            // Wait until all overlays from snapshot is loaded
                            (msg.text().indexOf(`Layers all loaded`) !== -1 && !go)
                        ) {
                            console.log('App was loaded, generating PNG');
                            setTimeout(() => {
                                page.evaluate(`$('.leaflet-top').remove();$('#loadscreen').remove();`).then(() => {
                                    page.screenshot({
                                        encoding: `base64`
                                    }).then(data => {
                                        let img = new Buffer.from(data, 'base64');
                                        localResponse.writeHead(200, {
                                            'Content-Type': 'image/png',
                                            'Content-Length': img.length
                                        });
                                        headless.destroy(browser);
                                        localResponse.end(img);
                                    }).catch(error => {
                                        localResponse.status(500);
                                        localResponse.send(error);
                                    });
                                }).catch(error => {
                                    localResponse.status(500);
                                    localResponse.send(error);
                                });
                            }, delay);
                        }
                    });

                    page.goto(url);
                }).catch(error => {
                    localResponse.status(500);
                    localResponse.send(error);
                });
            });
        }).catch(error => {
            console.error(error);
            localResponse.status(500);
            localResponse.send(`Error occured: ${error}`);
        });
    } else {
        localResponse.status(400);
        localResponse.send(`Errors occured: ${errorMessages.join(`,`)}`);
    }
};

router.get('/api/static/:db/:scheme?', returnPNGForStateSnapshot);
router.get('/api/static/:db/:scheme?/', returnPNGForStateSnapshot);

module.exports = router;
<|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: process.env.EDTR_SOURCE || "https://cdn.jsdelivr.net/gh/schul-cloud/edtrio@b12fe5b3db03dc98b6bac2ef44229c660156167a/dist/index.js",
            backendUrl: process.env.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>backend url change<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) {
        return res.render('topic/topic-edtr', {
            edtrSource: process.env.EDTR_SOURCE || "https://cdn.jsdelivr.net/gh/schul-cloud/edtrio@b12fe5b3db03dc98b6bac2ef44229c660156167a/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>'use strict';

var fs = require('fs-extra');

var fileUtils = rootRequire('utils/file-utils');
var authenticationUtils = rootRequire('utils/authentication-utils');
var model = rootRequire('models/model');
var cloudstorage = rootRequire('libs/cloudstorage/cloudstorage');

module.exports.controller = function(app) {

    /**
     * GET /tracks/:id
     * Return the artist associated with the specified ID
     **/
    app.get('/tracks/:id', function(req, res) {

        var trackId = req.params.id;
        model.Track.find({
            where: {
                id: trackId
            },
            include: [{
                model: model.Artist,
                as: 'Remixer'
            }, {
                model: model.Artist,
                as: 'Producer'
            }, {
                model: model.Release,
                include: [{
                    model: model.Label
                }]
            }]
        }).then(function(track) {
            res.send(track);
        });
    });

    function urlForProperty(trackId, property, callback) {
        var trackId = req.params.id;
        model.Track.find({
            where: {
                id: trackId
            }
        }).then(function(track) {
            if (track[property]) {
                cloudstorage.createSignedUrl(track[property], "GET", 20, callback);
            } else {
                callback(new Error('Track property does not exist'));
            }
        });
    }

    /**
     * GET /tracks/:id/snippet/mp3
     * Return the snippet for the track in mp3 format
     **/
    app.get('/tracks/:id/snippet/mp3', function(req, res) {

        var trackId = req.params.id;
        urlForProperty(trackId, 'snippetPath', function(err, url) {
            if (err) {
                //throw err;
                err.status = 404;
                err.message = "Snippet not found";
                return next(err);
            }
            res.redirect(url);
        });
    });

    /**
     * GET /tracks/:id/snippet/ogg
     * Return the snippet for the track in ogg format
     **/
    app.get('/tracks/:id/snippet/ogg', function(req, res) {

        var trackId = req.params.id;
        urlForProperty(trackId, 'oggSnippetPath', function(err, url) {
            if (err) {
                //throw err;
                err.status = 404;
                err.message = "Ogg snippet not found";
                return next(err);
            }
            res.redirect(url);
        });
    });

    /**
     * GET /tracks/:id/waveform
     * Return the waveform file for the track in json format
     **/
    app.get('/tracks/:id/waveform', function(req, res) {

        var trackId = req.params.id;
        urlForProperty(trackId, 'waveformPath', function(err, url) {
            if (err) {
                //throw err;
                err.status = 404;
                err.message = "Waveform file not found";
                return next(err);
            }
            res.redirect(url);
        });
    });

    /**
     * GET /tracks/:id/download/lossless
     * Return the complete lossless file for the track in wav format
     * TODO: Be sure that the authenticated user bought the track
     **/
    app.get('/tracks/:id/download/lossless', function(req, res) {

        var trackId = req.params.id;
        urlForProperty(trackId, 'path', function(err, url) {
            if (err) {
                //throw err;
                err.status = 404;
                err.message = "Lossless file not found";
                return next(err);
            }
            res.redirect(url);
        });
    });

    /**
     * GET /tracks/:id/download/mp3
     * Return the complete file for the track in mp3 format
     * TODO: Be sure that the authenticated user bought the track
     **/
    app.get('/tracks/:id/download/mp3', function(req, res) {

        var trackId = req.params.id;
        urlForProperty(trackId, 'mp3Path', function(err, url) {
            if (err) {
                //throw err;
                err.status = 404;
                err.message = "Mp3 file not found";
                return next(err);
            }
            res.redirect(url);
        });
    });

} /* End of tracks controller */<commit_msg>Fix minor bug track API<commit_after>'use strict';

var fs = require('fs-extra');

var fileUtils = rootRequire('utils/file-utils');
var authenticationUtils = rootRequire('utils/authentication-utils');
var model = rootRequire('models/model');
var cloudstorage = rootRequire('libs/cloudstorage/cloudstorage');

module.exports.controller = function(app) {

    /**
     * GET /tracks/:id
     * Return the artist associated with the specified ID
     **/
    app.get('/tracks/:id', function(req, res) {

        var trackId = req.params.id;
        model.Track.find({
            where: {
                id: trackId
            },
            include: [{
                model: model.Artist,
                as: 'Remixer'
            }, {
                model: model.Artist,
                as: 'Producer'
            }, {
                model: model.Release,
                include: [{
                    model: model.Label
                }]
            }]
        }).then(function(track) {
            res.send(track);
        });
    });

    function urlForProperty(trackId, property, callback) {
        model.Track.find({
            where: {
                id: trackId
            }
        }).then(function(track) {
            if (track[property]) {
                cloudstorage.createSignedUrl(track[property], "GET", 20, callback);
            } else {
                callback(new Error('Track property does not exist'));
            }
        });
    }

    /**
     * GET /tracks/:id/snippet/mp3
     * Return the snippet for the track in mp3 format
     **/
    app.get('/tracks/:id/snippet/mp3', function(req, res) {

        var trackId = req.params.id;
        urlForProperty(trackId, 'snippetPath', function(err, url) {
            if (err) {
                //throw err;
                err.status = 404;
                err.message = "Snippet not found";
                return next(err);
            }
            res.redirect(url);
        });
    });

    /**
     * GET /tracks/:id/snippet/ogg
     * Return the snippet for the track in ogg format
     **/
    app.get('/tracks/:id/snippet/ogg', function(req, res) {

        var trackId = req.params.id;
        urlForProperty(trackId, 'oggSnippetPath', function(err, url) {
            if (err) {
                //throw err;
                err.status = 404;
                err.message = "Ogg snippet not found";
                return next(err);
            }
            res.redirect(url);
        });
    });

    /**
     * GET /tracks/:id/waveform
     * Return the waveform file for the track in json format
     **/
    app.get('/tracks/:id/waveform', function(req, res) {

        var trackId = req.params.id;
        urlForProperty(trackId, 'waveformPath', function(err, url) {
            if (err) {
                //throw err;
                err.status = 404;
                err.message = "Waveform file not found";
                return next(err);
            }
            res.redirect(url);
        });
    });

    /**
     * GET /tracks/:id/download/lossless
     * Return the complete lossless file for the track in wav format
     * TODO: Be sure that the authenticated user bought the track
     **/
    app.get('/tracks/:id/download/lossless', function(req, res) {

        var trackId = req.params.id;
        urlForProperty(trackId, 'path', function(err, url) {
            if (err) {
                //throw err;
                err.status = 404;
                err.message = "Lossless file not found";
                return next(err);
            }
            res.redirect(url);
        });
    });

    /**
     * GET /tracks/:id/download/mp3
     * Return the complete file for the track in mp3 format
     * TODO: Be sure that the authenticated user bought the track
     **/
    app.get('/tracks/:id/download/mp3', function(req, res) {

        var trackId = req.params.id;
        urlForProperty(trackId, 'mp3Path', function(err, url) {
            if (err) {
                //throw err;
                err.status = 404;
                err.message = "Mp3 file not found";
                return next(err);
            }
            res.redirect(url);
        });
    });

} /* End of tracks controller */<|endoftext|>


<commit_before>var path = require('path');

var dipper = require('../dipper');

dipper.createApplication({
    packageConfigFile: path.resolve(__dirname, 'config.sample.json'),
    basePath: __dirname,
})
	.then(function (app) {
		console.log(app);
	})
	.catch(function (e) {
        console.log("Oh no! We've failed to set up your application:");
        console.log(e);
		console.log(e.stack);
	});

<commit_msg>Cleaned up sample.js a bit.<commit_after>var path = require('path');

var dipper = require('../dipper');

var config = {
    packageConfigFile: path.resolve(__dirname, 'config.sample.json'),
    basePath: __dirname,
};

dipper.createApplication(config)
    .then(function (services) {
        console.log(services);
    })
    .catch(function (e) {
        console.log("Oh no! We've failed to set up your application:");
        console.log(e);
        console.log(e.stack);
    });

<|endoftext|>


<commit_before>(function() {

    // how long server will take to send a response?
    var delay = 1000;

    // Init Backbone.CQRS
    // ------------------
    //
    // as we use default event Pattern
    // {
    //    'name': 'personChanged', // the unique name of this event
    //    'payload': {
    //        'id': 'someId', // the provided id should match the Backbone.Model.Id
    //        'anotherValue': 'something'
    //    }
    // }
    //
    // we can just init it:
    Backbone.CQRS.hub.init();

    // Wire up communication to/from server
    // ------------------------------------
    //
    // as we work clientside only we just push back the received commands
    //
    // HINT: you should pass cmds to server and forward events from server
    // ----- via websockets, flash, ...
    Backbone.CQRS.hub.on('commands', function(cmd) {
        var evt = cmd;

        // convert command to event
        if (evt.name === 'createPerson') {
            evt.name = 'personCreated';
            evt.payload.id = _.uniqueId('p'); // add a id on simulated 'serverside'
        } else if (evt.name === 'changePerson') {
            evt.name = 'personChanged';
        } else if (evt.name === 'deletePerson') {
            evt.name = 'personDeleted';
        }

        // send with some delay - better to see effect
        setTimeout(function() {
            Backbone.CQRS.hub.emit('events', evt);
        }, delay);
    });

    // Create a few EventDenormalizers
    // -------------------------------

    // personCreated event (override handle)
    var PersonCreateHandler = Backbone.CQRS.EventDenormalizer.extend({

        // bindings
        forModel: 'person',
        forEvent: 'personCreated',

        // as the 'personCreated' event creates a new model
        // we override the handle function
        handle: function(evt) {
            var person = new Person(evt.get('payload'));
            app.persons.add(person);
        }
    });
    var personCreateHandler = new PersonCreateHandler();

    // personChanged event (just go with defaults)
    var personChangedHandler = new Backbone.CQRS.EventDenormalizer({
        forModel: 'person',
        forEvent: 'personChanged'
    });

    // personCreated event (override apply)
    var PersonDeletedHandler = Backbone.CQRS.EventDenormalizer.extend({

        // bindings
        forModel: 'person',
        forEvent: 'personDeleted',

        // as the 'personDeleted' event destroys a model
        // we override the apply function
        apply: function(data, model) {
            // unbind it
            model.unbindCQRS();

            // destroy it
            model.destroy();
        }
    });
    var personDeletedHandler = new PersonDeletedHandler();

    // Create Backbone Stuff
    // ---------------------

    // model
    var Person = Backbone.Model.extend({
        modelName: 'person', // so denormalizers can resolve events to model

        initialize: function() {
            // bind this model to get event updates - a lot of magic ;)
            // not more to do the model gets updated now
            this.bindCQRS();
        },

        // as we don't have to sync the deletion to server as command already
        // took care of this we override the destroy function on model.
        // HINT: best would be to override Backbone.sync to only support GET
        destroy: function(options) {
            this.trigger('destroy', this, this.collection, options);
        }
    });

    // collection
    var Persons = Backbone.Collection.extend({
        model: Person
    });

    // view and templates
    var personItemTemplate = _.template('<%= personname %> delete edit');
    var personEditItemTemplate = _.template('<input id="name" type="text" value="<%= personname %>"></input><button id="changePerson">save</button>');

    var PersonItemView = Backbone.View.extend({

        tagName: 'li',
        className: 'personItem',

        initialize: function() {
            this.model.bind('change', this.render, this);
            this.model.bind('destroy', this.remove, this);
        },

        events: {
            'click .editPerson' : 'uiEditPerson',
            'click .deletePerson' : 'uiDeletePerson',
            'click #changePerson' : 'uiChangePerson'
        },

        // render edit input
        uiEditPerson: function(e) {
            e.preventDefault();
            this.model.editMode = true;
            this.render();
        },

        // send deletePerson command with id
        uiDeletePerson: function(e) {
            e.preventDefault();

            // CQRS command
            var cmd = new Backbone.CQRS.Command({
                name: 'deletePerson',
                payload: {
                    id: this.model.id
                }
            });

            // emit it
            cmd.emit();
        },

        // send changePerson command with new name
        uiChangePerson: function(e) {
            e.preventDefault();

            var name = this.$('#name').val();

            this.$('#name').val('');
            this.model.editMode = false;
            this.render();

            if (name) {

                // CQRS command
                var cmd = new Backbone.CQRS.Command({
                    name: 'changePerson',
                    payload: {
                        id: this.model.id,
                        personname: name
                    }
                });

                // emit it
                cmd.emit();
            }
        },

        render: function() {
            if (this.model.editMode) {
                $(this.el).html(personEditItemTemplate(this.model.toJSON()));
            } else {
                $(this.el).html(personItemTemplate(this.model.toJSON()));
            }
            return this;
        },

        remove: function() {
            $(this.el).fadeOut('slow');
        }

    });

    var IndexView =  Backbone.View.extend({

        el: '#index-view',

        initialize: function() {
            _.bindAll(this, 'addPerson');

            this.collection = app.persons;
            this.collection.bind('add', this.addPerson, this);
        },

        events: {
            'click #addPerson' : 'uiAddPerson'
        },

        // send createPerson command
        uiAddPerson: function(e) {
            e.preventDefault();

            var name = this.$('#name').val();

            if (name) {

                // CQRS command
                var cmd = new Backbone.CQRS.Command({
                    name: 'createPerson',
                    payload: { personname: name }
                });

                // emit it
                cmd.emit();
            }

            this.$('#name').val('');
        },

        render: function() {
            this.collection.each(this.addPerson);
        },

        addPerson: function(person) {
            var view = new PersonItemView({model: person});
            this.$('#persons').append(view.render().el);
        }

    });

    // Bootstrap Backbone
    // ------------------

    var app = {};
    var init = function() {
        app.persons = new Persons([
            {personname: 'Tim', id: _.uniqueId('p')},
            {personname: 'Ida', id: _.uniqueId('p')},
            {personname: 'Rob', id: _.uniqueId('p')}
        ]);

        var indexView = new IndexView();
        indexView.render();
    };

    // kick things off
    $(init);

})();<commit_msg>added comment on collection.fetch<commit_after>(function() {

    // how long server will take to send a response?
    var delay = 1000;

    // Init Backbone.CQRS
    // ------------------
    //
    // as we use default event Pattern
    // {
    //    'name': 'personChanged', // the unique name of this event
    //    'payload': {
    //        'id': 'someId', // the provided id should match the Backbone.Model.Id
    //        'anotherValue': 'something'
    //    }
    // }
    //
    // we can just init it:
    Backbone.CQRS.hub.init();

    // Wire up communication to/from server
    // ------------------------------------
    //
    // as we work clientside only we just push back the received commands
    //
    // HINT: you should pass cmds to server and forward events from server
    // ----- via websockets, flash, ...
    Backbone.CQRS.hub.on('commands', function(cmd) {
        var evt = cmd;

        // convert command to event
        if (evt.name === 'createPerson') {
            evt.name = 'personCreated';
            evt.payload.id = _.uniqueId('p'); // add a id on simulated 'serverside'
        } else if (evt.name === 'changePerson') {
            evt.name = 'personChanged';
        } else if (evt.name === 'deletePerson') {
            evt.name = 'personDeleted';
        }

        // send with some delay - better to see effect
        setTimeout(function() {
            Backbone.CQRS.hub.emit('events', evt);
        }, delay);
    });

    // Create a few EventDenormalizers
    // -------------------------------

    // personCreated event (override handle)
    var PersonCreateHandler = Backbone.CQRS.EventDenormalizer.extend({

        // bindings
        forModel: 'person',
        forEvent: 'personCreated',

        // as the 'personCreated' event creates a new model
        // we override the handle function
        handle: function(evt) {
            var person = new Person(evt.get('payload'));
            app.persons.add(person);
        }
    });
    var personCreateHandler = new PersonCreateHandler();

    // personChanged event (just go with defaults)
    var personChangedHandler = new Backbone.CQRS.EventDenormalizer({
        forModel: 'person',
        forEvent: 'personChanged'
    });

    // personCreated event (override apply)
    var PersonDeletedHandler = Backbone.CQRS.EventDenormalizer.extend({

        // bindings
        forModel: 'person',
        forEvent: 'personDeleted',

        // as the 'personDeleted' event destroys a model
        // we override the apply function
        apply: function(data, model) {
            // unbind it
            model.unbindCQRS();

            // destroy it
            model.destroy();
        }
    });
    var personDeletedHandler = new PersonDeletedHandler();

    // Create Backbone Stuff
    // ---------------------

    // model
    var Person = Backbone.Model.extend({
        modelName: 'person', // so denormalizers can resolve events to model

        initialize: function() {
            // bind this model to get event updates - a lot of magic ;)
            // not more to do the model gets updated now
            this.bindCQRS();
        },

        // as we don't have to sync the deletion to server as command already
        // took care of this we override the destroy function on model.
        // HINT: best would be to override Backbone.sync to only support GET
        destroy: function(options) {
            this.trigger('destroy', this, this.collection, options);
        }
    });

    // collection
    var Persons = Backbone.Collection.extend({
        model: Person
    });

    // view and templates
    var personItemTemplate = _.template('<%= personname %> delete edit');
    var personEditItemTemplate = _.template('<input id="name" type="text" value="<%= personname %>"></input><button id="changePerson">save</button>');

    var PersonItemView = Backbone.View.extend({

        tagName: 'li',
        className: 'personItem',

        initialize: function() {
            this.model.bind('change', this.render, this);
            this.model.bind('destroy', this.remove, this);
        },

        events: {
            'click .editPerson' : 'uiEditPerson',
            'click .deletePerson' : 'uiDeletePerson',
            'click #changePerson' : 'uiChangePerson'
        },

        // render edit input
        uiEditPerson: function(e) {
            e.preventDefault();
            this.model.editMode = true;
            this.render();
        },

        // send deletePerson command with id
        uiDeletePerson: function(e) {
            e.preventDefault();

            // CQRS command
            var cmd = new Backbone.CQRS.Command({
                name: 'deletePerson',
                payload: {
                    id: this.model.id
                }
            });

            // emit it
            cmd.emit();
        },

        // send changePerson command with new name
        uiChangePerson: function(e) {
            e.preventDefault();

            var name = this.$('#name').val();

            this.$('#name').val('');
            this.model.editMode = false;
            this.render();

            if (name) {

                // CQRS command
                var cmd = new Backbone.CQRS.Command({
                    name: 'changePerson',
                    payload: {
                        id: this.model.id,
                        personname: name
                    }
                });

                // emit it
                cmd.emit();
            }
        },

        render: function() {
            if (this.model.editMode) {
                $(this.el).html(personEditItemTemplate(this.model.toJSON()));
            } else {
                $(this.el).html(personItemTemplate(this.model.toJSON()));
            }
            return this;
        },

        remove: function() {
            $(this.el).fadeOut('slow');
        }

    });

    var IndexView =  Backbone.View.extend({

        el: '#index-view',

        initialize: function() {
            _.bindAll(this, 'addPerson');

            this.collection = app.persons;
            this.collection.bind('add', this.addPerson, this);
        },

        events: {
            'click #addPerson' : 'uiAddPerson'
        },

        // send createPerson command
        uiAddPerson: function(e) {
            e.preventDefault();

            var name = this.$('#name').val();

            if (name) {

                // CQRS command
                var cmd = new Backbone.CQRS.Command({
                    name: 'createPerson',
                    payload: { personname: name }
                });

                // emit it
                cmd.emit();
            }

            this.$('#name').val('');
        },

        render: function() {
            this.collection.each(this.addPerson);
        },

        addPerson: function(person) {
            var view = new PersonItemView({model: person});
            this.$('#persons').append(view.render().el);
        }

    });

    // Bootstrap Backbone
    // ------------------

    var app = {};
    var init = function() {
        app.persons = new Persons([
            // in a real app you would app.persons.fetch() and grab
            // the data via sync from server
            {personname: 'Tim', id: _.uniqueId('p')},
            {personname: 'Ida', id: _.uniqueId('p')},
            {personname: 'Rob', id: _.uniqueId('p')}
        ]);

        var indexView = new IndexView();
        indexView.render();
    };

    // kick things off
    $(init);

})();<|endoftext|>


<commit_before>var open = require('open');

var port = 5000;
var url = "http://localhost:" + port;

var express = require('express');
var app = express();

var static = express.static(__dirname + '/public');
app.use(static);

var oidc = require('./oidc.js');
oidc(url, app);

console.log("listening on " + url);
open(url);
app.listen(port);
<commit_msg>use path.join<commit_after>var open = require('open');
var path = require('path');

var port = 5000;
var url = "http://localhost:" + port;

var express = require('express');
var app = express();

var static = express.static(path.join(__dirname, 'public'));
app.use(static);

var oidc = require('./oidc.js');
oidc(url, app);

console.log("listening on " + url);
open(url);
app.listen(port);
<|endoftext|>


<commit_before>var copyrightCampaign = function(options) {
    options = typeof options !== 'undefined' ? options : { onBlackoutDayOnly: false, showOnlyOnce: true };
    var today = new Date();
    if (!(today.getDate() == 27 && today.getMonth() == 3) && options["onBlackoutDayOnly"]) {
        return;
    }

    if (typeof jQuery === "undefined") {
        var script_tag = document.createElement('script');
        script_tag.setAttribute("type","text/javascript");
        script_tag.setAttribute("src",
          "http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js");
        script_tag.onload = main; // Run main() once jQuery has loaded
        script_tag.onreadystatechange = function () { // Same thing but for IE
          if (this.readyState == 'complete' || this.readyState == 'loaded') main();
        }
        document.getElementsByTagName("head")[0].appendChild(script_tag);
    } else {
        main();
    }

    function main() {
        $(document).ready(function (e) {
            $("head").prepend("<link href='http://fonts.googleapis.com/css?family=Open+Sans:800|Gentium+Book+Basic' rel='stylesheet' type='text/css'>");

            var widgetStyle = '';

            $("head").prepend(widgetStyle);

            var widgetElement = '

Sulje

Järkeä
tekijänoikeuslakiin?

Allekirjoita kansalaisaloite kohtuullisemman tekijänoikeuslain puolesta.

Katso myös: Internetin musta päivä 23.4.

Sulje

Code by SC5

';
            $(document.body).append(widgetElement);

            function hideBox() {
                $("#copyright-campaign-box").hide();
                if(options["showOnlyOnce"] && typeof(Storage)!=="undefined") {
                    localStorage.boxClosed = true;
                }
            }

            if(options["showOnlyOnce"] && typeof(Storage)!=="undefined" && localStorage.boxClosed) {
                hideBox();
            }

            $(".close-copyright-campaign-box").click(function(e) {
                hideBox();
            });

            $(document).keyup(function(e) {
                if (e.keyCode == 27) {
                    hideBox();
                }
            });
        });
    }
};<commit_msg>Correct campaign date<commit_after>var copyrightCampaign = function(options) {
    options = typeof options !== 'undefined' ? options : { onBlackoutDayOnly: false, showOnlyOnce: true };
    var today = new Date();
    if (!(today.getDate() == 23 && today.getMonth() == 3) && options["onBlackoutDayOnly"]) {
        return;
    }

    if (typeof jQuery === "undefined") {
        var script_tag = document.createElement('script');
        script_tag.setAttribute("type","text/javascript");
        script_tag.setAttribute("src",
          "http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js");
        script_tag.onload = main; // Run main() once jQuery has loaded
        script_tag.onreadystatechange = function () { // Same thing but for IE
          if (this.readyState == 'complete' || this.readyState == 'loaded') main();
        }
        document.getElementsByTagName("head")[0].appendChild(script_tag);
    } else {
        main();
    }

    function main() {
        $(document).ready(function (e) {
            $("head").prepend("<link href='http://fonts.googleapis.com/css?family=Open+Sans:800|Gentium+Book+Basic' rel='stylesheet' type='text/css'>");

            var widgetStyle = '';

            $("head").prepend(widgetStyle);

            var widgetElement = '

Sulje

Järkeä
tekijänoikeuslakiin?

Allekirjoita kansalaisaloite kohtuullisemman tekijänoikeuslain puolesta.

Katso myös: Internetin musta päivä 23.4.

Sulje

Code by SC5

';
            $(document.body).append(widgetElement);

            function hideBox() {
                $("#copyright-campaign-box").hide();
                if(options["showOnlyOnce"] && typeof(Storage)!=="undefined") {
                    localStorage.boxClosed = true;
                }
            }

            if(options["showOnlyOnce"] && typeof(Storage)!=="undefined" && localStorage.boxClosed) {
                hideBox();
            }

            $(".close-copyright-campaign-box").click(function(e) {
                hideBox();
            });

            $(document).keyup(function(e) {
                if (e.keyCode == 27) {
                    hideBox();
                }
            });
        });
    }
};<|endoftext|>


<commit_before>var SandwichTime = (function () {
  'use strict';

  var slabPaint = {},
      dimensions = {
        width: 800,
        height: 600
      },
      level,
      canvas;

  slabPaint.tofu = function (context, offsetX, offsetY, slab) {
    var x = offsetX + slab.x,
        y = offsetY + slab.y,
        width = slab.width, height = slab.height;
    context.fillStyle = '#f7f2da';
    context.strokeStyle = '#57534b';
    context.strokeRect(x, y, width, height);
    context.fillRect(x, y, width, height);
  };

  // Slabs make up a sandwich. A slab can be a slice of bread or a filling.
  function Slab(x, y, width, height, paint) {
    var x, y, width, height, paint;
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.paint = paint;
  }

  // A hopper is a container for slabs. It sits at the bottom of a sandwich
  //  tower and collects slabs as they fall, building up the sandwich.
  function Hopper(x, y, width, height, thickness) {
    var x, y, width, height, thickness;
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.thickness = thickness;
  }
  Hopper.prototype.paint = function (context, offsetX, offsetY) {
    var x = offsetX + this.x,
        y = offsetY + this.y,
        thickness = this.thickness;
    context.fillStyle = '#7b775e';
    context.beginPath();
    context.moveTo(x, y);
    context.lineTo(x += thickness, y);
    context.lineTo(x, y += (this.height - thickness));
    context.lineTo(x += (this.width - 2*thickness), y);
    context.lineTo(x, y -= (this.height - thickness));
    context.lineTo(x += thickness, y);
    context.lineTo(x, y += this.height);
    context.lineTo(x -= this.width, y);
    context.lineTo(x, y -= this.height);
    context.closePath();
    context.fill();
  };

  // A tower is a vertical sequence of slabs with a hopper at the bottom to
  //  collect the slabs as the player makes them fall. When the final slab
  //  falls into the hopper, the sandwich is complete.
  function Tower(slabs, hopper) {
    var slabs;
    this.slabs = slabs;
    this.hopper = hopper;
  }
  Tower.prototype.paint = function (context) {
    var i, slab;
    for (i = 0; i < this.slabs.length; ++i) {
      slab = this.slabs[i];
      slab.paint(context, this.x, this.y, slab);
    }
    this.hopper.paint(context, this.x, this.y);
  };

  // randrange is much like Python's random.randrange: it returns a random
  //  integer in the range [low, high). If only one argument is passed, it
  //  is taken as high and low is set to zero. Unlike the Python version,
  //  this function returns no error or warning if low >= high.
  function randrange(low, high) {
    if (high === undefined) {
      high = low;
      low = 0;
    }
    return low + Math.floor(Math.random() * (high - low));
  }

  function makeRandomTower(grid) {
    var slabs = [],
        paint = slabPaint.tofu,
        minNumSlabs = 2,
        maxNumSlabs = 5,
        slabHeight = 15,
        minSlabWidth = 30,
        maxSlabWidth = 80,
        minGap = 30,
        maxGap = 60,
        hopperThickness = 8,
        numSlabs, slabWidth, hopperWidth, hopperHeight,
        i, slab, hopper, x, y, tower;
    // The local coordinates (x, y) designate a point within the tower's
    //  bounding rectangle. The top left corner is (0, 0).
    x = y = 0;
    // Move to the top left corner of the top slab.
    x += hopperThickness;
    slabWidth = randrange(minSlabWidth, maxSlabWidth);
    numSlabs = randrange(minNumSlabs, maxNumSlabs + 1);
    for (i = 0; i < numSlabs; ++i) {
      slab = new Slab(x, y, slabWidth, slabHeight, paint);
      slabs.push(slab);
      y += slabHeight + randrange(minGap, maxGap + 1);
    }
    // Move to the top left corner of the hopper.
    x -= hopperThickness;
    hopperWidth = 2*hopperThickness + slabWidth;
    hopperHeight = numSlabs*slabHeight + hopperThickness;
    hopper = new Hopper(x, y, hopperWidth, hopperHeight, hopperThickness);
    tower = new Tower(slabs, hopper);
    // Calculate the lower right corner of the bounding rectangle.
    tower.width = x + hopperWidth;
    tower.height = y + hopperHeight;
    return tower;
  }

  function Grid(width, height) {
    this.width = width;
    this.height = height;
  }

  function Level(grid, towers, color) {
    this.grid = grid;
    this.towers = towers;
    this.color = color;
    this.width = grid.width;
    this.height = grid.height;
  }
  Level.prototype.paint = function (context) {
    var i, tower;
    // Background.
    context.fillStyle = this.color.background;
    context.fillRect(0, 0, this.width, this.height);
    // Towers.
    for (i = 0; i < this.towers.length; ++i) {
      tower = this.towers[i];
      tower.paint(context);
    }
  };

  function makeRandomLevel(width, height) {
    var grid = new Grid(width, height),
        color = { background: '#073157' },
        gridArea = width * height,
        minTowerDensity = 0.2,
        towers = [],
        tower, towerArea = 0,
        x, y, okay, i, other, minDistance;
    while (towerArea / gridArea < minTowerDensity) {
      tower = makeRandomTower(grid);
      towerArea += tower.width * tower.height;
      towers.push(tower);
      // Position the current tower without getting too close to
      //  earlier towers.
      minDistance = 0;
      okay = false;
      while (!okay) {
        okay = true;
        x = randrange(width - tower.width);
        y = randrange(height - tower.height);
        for (i = towers.length - 2; i >= 0; --i) {
          other = towers[i];
          if (x + tower.width + minDistance <= other.x ||
              other.x + other.width + minDistance <= x) {
            continue;
          }
          if (y + tower.height + minDistance <= other.y ||
              other.y + other.height + minDistance <= y) {
            continue;
          }
          okay = false;
          break;
        }
      }
      tower.x = x;
      tower.y = y;
    }
    return new Level(grid, towers, color);
  }

  function load() {
    canvas = document.createElement('canvas');
    document.getElementById('levelBox').appendChild(canvas);
    canvas.width = dimensions.width;
    canvas.height = dimensions.height;
    level = makeRandomLevel(dimensions.width, dimensions.height);
    level.paint(canvas.getContext('2d'));
    console.log('ready');
  }

  return {
    load: load
  };
})();

window.onload = SandwichTime.load;
<commit_msg>Count and limit attempts to avoid overlap<commit_after>var SandwichTime = (function () {
  'use strict';

  var slabPaint = {},
      dimensions = {
        width: 800,
        height: 600
      },
      level,
      canvas;

  slabPaint.tofu = function (context, offsetX, offsetY, slab) {
    var x = offsetX + slab.x,
        y = offsetY + slab.y,
        width = slab.width, height = slab.height;
    context.fillStyle = '#f7f2da';
    context.fillRect(x, y, width, height);
  };

  // Slabs make up a sandwich. A slab can be a slice of bread or a filling.
  function Slab(x, y, width, height, paint) {
    var x, y, width, height, paint;
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.paint = paint;
  }

  // A hopper is a container for slabs. It sits at the bottom of a sandwich
  //  tower and collects slabs as they fall, building up the sandwich.
  function Hopper(x, y, width, height, thickness) {
    var x, y, width, height, thickness;
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.thickness = thickness;
  }
  Hopper.prototype.paint = function (context, offsetX, offsetY) {
    var x = offsetX + this.x,
        y = offsetY + this.y,
        thickness = this.thickness;
    context.fillStyle = '#7b775e';
    context.beginPath();
    context.moveTo(x, y);
    context.lineTo(x += thickness, y);
    context.lineTo(x, y += (this.height - thickness));
    context.lineTo(x += (this.width - 2*thickness), y);
    context.lineTo(x, y -= (this.height - thickness));
    context.lineTo(x += thickness, y);
    context.lineTo(x, y += this.height);
    context.lineTo(x -= this.width, y);
    context.lineTo(x, y -= this.height);
    context.closePath();
    context.fill();
  };

  // A tower is a vertical sequence of slabs with a hopper at the bottom to
  //  collect the slabs as the player makes them fall. When the final slab
  //  falls into the hopper, the sandwich is complete.
  function Tower(slabs, hopper) {
    var slabs;
    this.slabs = slabs;
    this.hopper = hopper;
  }
  Tower.prototype.paint = function (context) {
    var i, slab;
    for (i = 0; i < this.slabs.length; ++i) {
      slab = this.slabs[i];
      slab.paint(context, this.x, this.y, slab);
    }
    this.hopper.paint(context, this.x, this.y);
  };

  // randrange is much like Python's random.randrange: it returns a random
  //  integer in the range [low, high). If only one argument is passed, it
  //  is taken as high and low is set to zero. Unlike the Python version,
  //  this function returns no error or warning if low >= high.
  function randrange(low, high) {
    if (high === undefined) {
      high = low;
      low = 0;
    }
    return low + Math.floor(Math.random() * (high - low));
  }

  function makeRandomTower(grid) {
    var slabs = [],
        paint = slabPaint.tofu,
        minNumSlabs = 2,
        maxNumSlabs = 5,
        slabHeight = 15,
        minSlabWidth = 30,
        maxSlabWidth = 80,
        minGap = 30,
        maxGap = 60,
        hopperThickness = 8,
        numSlabs, slabWidth, hopperWidth, hopperHeight,
        i, slab, hopper, x, y, tower;
    // The local coordinates (x, y) designate a point within the tower's
    //  bounding rectangle. The top left corner is (0, 0).
    x = y = 0;
    // Move to the top left corner of the top slab.
    x += hopperThickness;
    slabWidth = randrange(minSlabWidth, maxSlabWidth);
    numSlabs = randrange(minNumSlabs, maxNumSlabs + 1);
    for (i = 0; i < numSlabs; ++i) {
      slab = new Slab(x, y, slabWidth, slabHeight, paint);
      slabs.push(slab);
      y += slabHeight + randrange(minGap, maxGap + 1);
    }
    // Move to the top left corner of the hopper.
    x -= hopperThickness;
    hopperWidth = 2*hopperThickness + slabWidth;
    hopperHeight = numSlabs*slabHeight + hopperThickness;
    hopper = new Hopper(x, y, hopperWidth, hopperHeight, hopperThickness);
    tower = new Tower(slabs, hopper);
    // Calculate the lower right corner of the bounding rectangle.
    tower.width = x + hopperWidth;
    tower.height = y + hopperHeight;
    return tower;
  }

  function Grid(width, height) {
    this.width = width;
    this.height = height;
  }

  function Level(grid, towers, color) {
    this.grid = grid;
    this.towers = towers;
    this.color = color;
    this.width = grid.width;
    this.height = grid.height;
  }
  Level.prototype.paint = function (context) {
    var i, tower;
    // Background.
    context.fillStyle = this.color.background;
    context.fillRect(0, 0, this.width, this.height);
    // Towers.
    for (i = 0; i < this.towers.length; ++i) {
      tower = this.towers[i];
      tower.paint(context);
    }
  };

  function makeRandomLevel(width, height) {
    var grid = new Grid(width, height),
        color = { background: '#073157' },
        gridArea = width * height,
        minTowerDensity = 0.15,
        towers = [],
        tower, towerArea = 0,
        x, y, okay, count, countLimit, i, other, minDistance;
    while (towerArea / gridArea < minTowerDensity) {
      tower = makeRandomTower(grid);
      towerArea += tower.width * tower.height;
      towers.push(tower);
      // Position the current tower without getting too close to
      //  earlier towers.
      minDistance = 50;
      okay = false;
      count = 0;
      countLimit = 100;
      while (++count <= countLimit && !okay) {
        okay = true;
        x = randrange(width - tower.width);
        y = randrange(height - tower.height);
        for (i = towers.length - 2; i >= 0; --i) {
          other = towers[i];
          if (x + tower.width + minDistance <= other.x ||
              other.x + other.width + minDistance <= x) {
            continue;
          }
          if (y + tower.height + minDistance <= other.y ||
              other.y + other.height + minDistance <= y) {
            continue;
          }
          okay = false;
          break;
        }
      }
      if (count > countLimit) {
        console.log('reached limit', countLimit);
      }
      tower.x = x;
      tower.y = y;
    }
    return new Level(grid, towers, color);
  }

  function load() {
    canvas = document.createElement('canvas');
    document.getElementById('levelBox').appendChild(canvas);
    canvas.width = dimensions.width;
    canvas.height = dimensions.height;
    level = makeRandomLevel(dimensions.width, dimensions.height);
    level.paint(canvas.getContext('2d'));
    console.log('ready');
  }

  return {
    load: load
  };
})();

window.onload = SandwichTime.load;
<|endoftext|>


<commit_before>/**
  * Constructs a 256-bit color that can be displayed in a pixel, given three primary color components.
  * @param red optional non-negative integer ≤ 255, defaults to 0; the red   component of this color
  * @param grn optional non-negative integer ≤ 255, defaults to 0; the green component of this color
  * @param blu optional non-negative integer ≤ 255, defaults to 0; the blue  component of this color
  */
function Color(red, grn, blu) {
  var self = this;

  self.red   = (typeof red === 'number') ? red : 0;
  self.green = (typeof grn === 'number') ? grn : 0;
  self.blue  = (typeof blu === 'number') ? blu : 0;

  /**
    * The HSV-space hue of this color, or what "color" this color is.
    */
  self.hsv_hue = (function () {
    return 0; // FIX THIS
  })();
  /**
    * The vividness of this color. A lower saturation means the color is closer to white,
    * a higher saturation means the color is more true to its hue.
    */
  self.hsv_sat = (function () {
    return 0; // FIX THIS
  })();
  /**
    * The brightness of this color. A lower value means the color is closer to black, a higher
    * value means the color is more true to its hue.
    * The HSV-space value ("brightness") of this color is equivalent to the ratio of the
    * brightest RGB-component's value to 255, as a percentage.
    */
  self.hsv_val = (function () {
    return Math.max(self.red, self.green, self.blue) / 255;
  })();
  /**
    * The Hue of this color. Identical to `this.hsv_hue`.
    */
  self.hsl_hue = (function () {
    return self.hsv_hue;
  })();
  /**
    * The amount of "color" in the color. A lower saturation means the color is more grayer,
    * a higher saturation means the color is more colorful.
    */
  self.hsl_sat = (function () {
    return 0; // FIX THIS
  })();
  /**
    * How "white" or "black" the color is. A lower luminosity means the color is closer to black,
    * a higher luminosity means the color is closer to white.
    */
  self.hsl_lum = (function () {
    return 0; // FIX THIS
  })();
}

/**
  * Returns a new color that is the complement of this color.
  * The complement of a color is the difference between that color and white (#fff).
  * @return a new Color object that corresponds to this color's complement
  */
Color.prototype.complement = function () {
  return new Color(255 - this.red, 255 - this.green, 255 - this.blue);
}

/**
  * Returns a new color that is the inverse of this color.
  * The inverse of a color is that color with a hue rotation of 180 degrees.
  * @return a new Color object that corresponds to this color's inverse
  */
Color.prototype.invert = function () {
  var newhue = (this.getHSV_hue() + 180) % 360;
  return Color.newColorHSV(newhue, this.getHSV_sat(), this.getHSV_val());
}

/**
  * Makes a new color that is a brighter version of this color by a percentage.
  * 1.0 corresponds to making it completely white (#fff), and 0% keeps this color the same.
  * A negative parameter will darken this color (see `this.darken(p)`).
  * @param p must be between -1.0 and 1.0; the percentage by which to lighten this color
  * @return  a new Color object that corresponds to this color brightened by a percentage `p`
  */
Color.prototype.brighten = function (p) {
  // return Color.newColorHSL(this.getHSL_hue(), this.getHSL_sat(), this.getHSL_val() + p);
}
/**
  * Makes a new color that is a darker version of this color by a percentage.
  * 1.0 corresponds to making it completely black (#000), abd 0% keeps this color the same.
  * A negative parameter will lighten this color (see `this.brighten(p)`).
  * @param p must be between -1.0 and 1.0; the percentage by which to darken this color
  * @return  a new Color object that corresponds to this color darkened by a percentage `p`
  */
Color.prototype.darken = function (p) {
  // return Color.newColorHSL(this.getHSL_hue(), this.getHSL_sat(), this.getHSL_val() - p);
  // return this.brighten(-p);
}

/**
  * Returns a string representation of this color.
  * If `space === 'hsv'`, returns `hsv(h, s, v)`
  * If `space === 'hsl'`, returns `hsl(h, s, l)`
  * If `space === 'hex'`, returns `#RRGGBB`
  * If `space === 'rgb'`, or if no param is given, returns `rgb(r, g, b)`
  * @param space optional ('rgb'): a string representing the space in which this color exists
  * @return      a string representing this color.
  */
Color.prototype.toString = function (space) {
  function toHex(n) {
    n = parseInt(n,10);
    if (isNaN(n)) return '00';
    n = Util.bound(n, 0, 255);
    return '0123456789ABCDEF'.charAt((n - n % 16) / 16) + '0123456789ABCDEF'.charAt(n % 16);
  }
  if (space === 'hex') return '#' + toHex(this.red) + toHex(this.green) + toHex(this.blue);
  if (space === 'hsv') return 'hsv(' + this.hsv_hue + ', ' + this.hsv_sat + ', ' + this.hsv_val + ')';
  if (space === 'hsl') return 'hsl(' + this.hsl_hue + ', ' + this.hsl_sat + ', ' + this.hsl_lum + ')';
                       return 'rgb(' + this.red     + ', ' + this.green   + ', ' + this.blue    + ')';
}

/**
  * Returns a new Color object, given a string of the form `rgb(r,g,b)` or `rgb(r, g, b)`,
  * where `r`, `g`, and `b` are decimal RGB components (in base 10, out of 255).
  * @param rgb_string a string of the form `rgb(r,g,b)` or `rgb(r, g, b)`
  */
Color.newColorRGBstring = function (rgb_string) {
  var splitted = rgb_string.slice(4, -1).split(',');
  return new Color(+splitted[0], +splitted[1], +splitted[2]);
}

/**
  * Returns a new Color object, given hue, saturation, and value in HSV-space.
  *
  * Takes HSV-components as number arguments and returns a new Color object with
  * RGB-components, each a base-10 number from 0 to 255.
  *
  * Photoshop, etc. represents the saturation and "brightness" (value) values from 0 to 100,
  * however, the saturation and value are calculated from a range of 0 to 1. Make that conversion
  * before calling this method.
  *
  * Ported from the excellent java algorithm by Eugene Vishnevsky at:
  * http://www.cs.rit.edu/~ncs/color/t_convert.html
  *
  * @param hue must be between 0 and 360; hue in HSV-space
  * @param sat must be between 0.0 and 1.0; saturation in HSV-space
  * @param val must be between 0.0 and 1.0; brightness in HSV-space
  * @return    a new Color object with hsv(`h`, `s`, `v`)
  */
Color.newColorHSV = function (hue, sat, val) {
  var red, grn, blu;
  if (sat === 0) {
    // achromatic (grey)
    red = grn = blu = val;
  } else {
    var h = hue / 60; // sector 0 to 5
    var i = Math.floor(h);
    var f = h - i; // factorial part of h
    var p = val * (1 - sat),
        q = val * (1 - sat * f),
        t = val * (1 - sat * (1 - f));
    switch(i) {
      case 0:  red = val; grn = t;   blu = p;   break;
      case 1:  red = q;   grn = val; blu = p;   break;
      case 2:  red = p;   grn = val; blu = t;   break;
      case 3:  red = p;   grn = q;   blu = val; break;
      case 4:  red = t;   grn = p;   blu = val; break;
      default: red = val; grn = p;   blu = q;   break; // case 5
    }
  }
  red = Math.round(red * 255);
  grn = Math.round(grn * 255);
  blu = Math.round(blu * 255);

  var returned = new Color(red, grn, blu);
  returned.hsv_hue = hue;
  returned.hsv_sat = sat;
  returned.hsv_val = val;
  return returned;
}
/**
  * Returns a new Color object, given hue, saturation, and luminosity.
  * @param hue must be between 0 and 360; same as the `hue` in HSV-space
  * @param sat must be between 0.0 and 1.0; saturation in HSL-space
  * @param lum must be between 0.0 and 1.0; luminosity in HSL-space
  * @return    a new Color object with hsl(`hue`, `sat`, `lum`)
  */
Color.newColorHSL = function (hue, sat, lum) {
  return new Color();// FIX THIS
}

/**
  * Mixes (averages) two colors, with a given weight favoring the first color.
  * If `w == 1.0`, this method will return `color1`. `w == 0.0`, will return `color2`.
  * `w == 0.5` (default if omitted) will result in a perfectly even mix.
  * @param color1 required Color object; the first color
  * @param color2 required Color object; the second color
  * @param w      optional number between 0.0 and 1.0, defaults to 0.5; the weight favoring the first color
  * @return       a mix of the two given colors
  */
Color.mix = function (color1, color2, w) {
  w = (typeof w === 'number') ? w : 0.5;
  var r = Math.round(Util.average(color1.red,   color2.red,   w));
  var g = Math.round(Util.average(color1.green, color2.green, w));
  var b = Math.round(Util.average(color1.blue,  color2.blue,  w));
  return new Color(r, g, b);
}
<commit_msg>fixed number typing<commit_after>/**
  * Constructs a 256-bit color that can be displayed in a pixel, given three primary color components.
  * @param red optional non-negative integer ≤ 255, defaults to 0; the red   component of this color
  * @param grn optional non-negative integer ≤ 255, defaults to 0; the green component of this color
  * @param blu optional non-negative integer ≤ 255, defaults to 0; the blue  component of this color
  */
function Color(red, grn, blu) {
  var self = this;

  self.red   = +red || 0;
  self.green = +grn || 0;
  self.blue  = +blu || 0;

  /**
    * The HSV-space hue of this color, or what "color" this color is.
    */
  self.hsv_hue = (function () {
    return 0; // FIX THIS
  })();
  /**
    * The vividness of this color. A lower saturation means the color is closer to white,
    * a higher saturation means the color is more true to its hue.
    */
  self.hsv_sat = (function () {
    return 0; // FIX THIS
  })();
  /**
    * The brightness of this color. A lower value means the color is closer to black, a higher
    * value means the color is more true to its hue.
    * The HSV-space value ("brightness") of this color is equivalent to the ratio of the
    * brightest RGB-component's value to 255, as a percentage.
    */
  self.hsv_val = (function () {
    return Math.max(self.red, self.green, self.blue) / 255;
  })();
  /**
    * The Hue of this color. Identical to `this.hsv_hue`.
    */
  self.hsl_hue = (function () {
    return self.hsv_hue;
  })();
  /**
    * The amount of "color" in the color. A lower saturation means the color is more grayer,
    * a higher saturation means the color is more colorful.
    */
  self.hsl_sat = (function () {
    return 0; // FIX THIS
  })();
  /**
    * How "white" or "black" the color is. A lower luminosity means the color is closer to black,
    * a higher luminosity means the color is closer to white.
    */
  self.hsl_lum = (function () {
    return 0; // FIX THIS
  })();
}

/**
  * Returns a new color that is the complement of this color.
  * The complement of a color is the difference between that color and white (#fff).
  * @return a new Color object that corresponds to this color's complement
  */
Color.prototype.complement = function () {
  return new Color(255 - this.red, 255 - this.green, 255 - this.blue);
}

/**
  * Returns a new color that is the inverse of this color.
  * The inverse of a color is that color with a hue rotation of 180 degrees.
  * @return a new Color object that corresponds to this color's inverse
  */
Color.prototype.invert = function () {
  var newhue = (this.getHSV_hue() + 180) % 360;
  return Color.newColorHSV(newhue, this.hsv_sat, this.hsv_val);
}

/**
  * Makes a new color that is a brighter version of this color by a percentage.
  * 1.0 corresponds to making it completely white (#fff), and 0% keeps this color the same.
  * A negative parameter will darken this color (see `this.darken(p)`).
  * @param p must be between -1.0 and 1.0; the percentage by which to lighten this color
  * @return  a new Color object that corresponds to this color brightened by a percentage `p`
  */
Color.prototype.brighten = function (p) {
  // return Color.newColorHSL(this.hsl_hue, this.hsl_sat, this.hsl_val + p);
}
/**
  * Makes a new color that is a darker version of this color by a percentage.
  * 1.0 corresponds to making it completely black (#000), abd 0% keeps this color the same.
  * A negative parameter will lighten this color (see `this.brighten(p)`).
  * @param p must be between -1.0 and 1.0; the percentage by which to darken this color
  * @return  a new Color object that corresponds to this color darkened by a percentage `p`
  */
Color.prototype.darken = function (p) {
  // return Color.newColorHSL(this.hsl_hue, this.hsl_sat, this.hsl_val - p);
  // return this.brighten(-p);
}

/**
  * Returns a string representation of this color.
  * If `space === 'hsv'`, returns `hsv(h, s, v)`
  * If `space === 'hsl'`, returns `hsl(h, s, l)`
  * If `space === 'hex'`, returns `#RRGGBB`
  * If `space === 'rgb'`, or if no param is given, returns `rgb(r, g, b)`
  * @param space optional ('rgb'): a string representing the space in which this color exists
  * @return      a string representing this color.
  */
Color.prototype.toString = function (space) {
  function toHex(n) {
    n = +n || 0;
    n = Util.bound(n, 0, 255);
    return '0123456789ABCDEF'.charAt((n - n % 16) / 16) + '0123456789ABCDEF'.charAt(n % 16);
  }
  if (space === 'hex') return '#' + toHex(this.red) + toHex(this.green) + toHex(this.blue);
  if (space === 'hsv') return 'hsv(' + this.hsv_hue + ', ' + this.hsv_sat + ', ' + this.hsv_val + ')';
  if (space === 'hsl') return 'hsl(' + this.hsl_hue + ', ' + this.hsl_sat + ', ' + this.hsl_lum + ')';
                       return 'rgb(' + this.red     + ', ' + this.green   + ', ' + this.blue    + ')';
}

/**
  * Returns a new Color object, given a string of the form `rgb(r,g,b)` or `rgb(r, g, b)`,
  * where `r`, `g`, and `b` are decimal RGB components (in base 10, out of 255).
  * @param rgb_string a string of the form `rgb(r,g,b)` or `rgb(r, g, b)`
  */
Color.newColorRGBstring = function (rgb_string) {
  var splitted = rgb_string.slice(4, -1).split(',');
  return new Color(+splitted[0], +splitted[1], +splitted[2]);
}

/**
  * Returns a new Color object, given hue, saturation, and value in HSV-space.
  *
  * Takes HSV-components as number arguments and returns a new Color object with
  * RGB-components, each a base-10 number from 0 to 255.
  *
  * Photoshop, etc. represents the saturation and "brightness" (value) values from 0 to 100,
  * however, the saturation and value are calculated from a range of 0 to 1. Make that conversion
  * before calling this method.
  *
  * Ported from the excellent java algorithm by Eugene Vishnevsky at:
  * http://www.cs.rit.edu/~ncs/color/t_convert.html
  *
  * @param hue must be between 0 and 360; hue in HSV-space
  * @param sat must be between 0.0 and 1.0; saturation in HSV-space
  * @param val must be between 0.0 and 1.0; brightness in HSV-space
  * @return    a new Color object with hsv(`h`, `s`, `v`)
  */
Color.newColorHSV = function (hue, sat, val) {
  var red, grn, blu;
  if (sat === 0) {
    // achromatic (grey)
    red = grn = blu = val;
  } else {
    var h = hue / 60; // sector 0 to 5
    var i = Math.floor(h);
    var f = h - i; // factorial part of h
    var p = val * (1 - sat),
        q = val * (1 - sat * f),
        t = val * (1 - sat * (1 - f));
    switch(i) {
      case 0:  red = val; grn = t;   blu = p;   break;
      case 1:  red = q;   grn = val; blu = p;   break;
      case 2:  red = p;   grn = val; blu = t;   break;
      case 3:  red = p;   grn = q;   blu = val; break;
      case 4:  red = t;   grn = p;   blu = val; break;
      default: red = val; grn = p;   blu = q;   break; // case 5
    }
  }
  red = Math.round(red * 255);
  grn = Math.round(grn * 255);
  blu = Math.round(blu * 255);

  var returned = new Color(red, grn, blu);
  returned.hsv_hue = hue;
  returned.hsv_sat = sat;
  returned.hsv_val = val;
  return returned;
}
/**
  * Returns a new Color object, given hue, saturation, and luminosity.
  * @param hue must be between 0 and 360; same as the `hue` in HSV-space
  * @param sat must be between 0.0 and 1.0; saturation in HSL-space
  * @param lum must be between 0.0 and 1.0; luminosity in HSL-space
  * @return    a new Color object with hsl(`hue`, `sat`, `lum`)
  */
Color.newColorHSL = function (hue, sat, lum) {
  return new Color();// FIX THIS
}

/**
  * Mixes (averages) two colors, with a given weight favoring the first color.
  * If `w == 1.0`, this method will return `color1`. `w == 0.0`, will return `color2`.
  * `w == 0.5` (default if omitted) will result in a perfectly even mix.
  * @param color1 required Color object; the first color
  * @param color2 required Color object; the second color
  * @param w      optional number between 0.0 and 1.0, defaults to 0.5; the weight favoring the first color
  * @return       a mix of the two given colors
  */
Color.mix = function (color1, color2, w) {
  w = (typeof w === 'number') ? w : 0.5;
  var r = Math.round(Util.average(color1.red,   color2.red,   w));
  var g = Math.round(Util.average(color1.green, color2.green, w));
  var b = Math.round(Util.average(color1.blue,  color2.blue,  w));
  return new Color(r, g, b);
}
<|endoftext|>


<commit_before>/*
 * select.js
 * @author Jared Smith <jasmith79@gmail.com>
 * @copyright 2017
 * @license MIT
 *
 * JavaScript file for ui-component-select
 */

import List from 'list';
import { CoreElementMixin, extractType } from 'core';

const SelectMixin = {
  // Overrides the List version
  select (opt) {
    if (extractType(opt) === 'Number') {
      let option = this.options[opt];
      if (option) {
        this.options.forEach(o => {
          o.removeAttribute('selected');
        });
        option.setAttribute('selected', true);
        // option.classList.remove('is-hidden');
        this.selected = option;
      }

      return this.close();
    } else {
      return this.select(this.options.indexOf(opt));
    }
  },

  open () {
    this.removeAttribute('collapsed')
    // this.options.forEach(o => o.show());
    return this;
  },

  close () {
    this.setAttribute('collapsed', true);
    // this.options.forEach(o => { if (o !== this.selected) o.hide() });
    return this;
  },

  selected: null
};

const Select = function Select(el=document.createElement('div')) {
  el.classList.add('ui-component-select');
  el.options = Array.from(el.querySelectorAll('.ui-component-option'));
  el.options.forEach(x => {
    x.classList.add('ui-component-list-item');
    let arrow = document.createElement('div');
    arrow.classList.add('ui-component-down-arrow');
    x.appendChild(arrow);
  });

  let pendingClose = null;
  el.addEventListener('click', () => el.open());
  el.addEventListener('mouseleave', () => pendingClose = setTimeout(() => { el.close(); }, 700));
  el.addEventListener('mouseenter', () => clearTimeout(pendingClose));

  Object.assign(List(el), SelectMixin);
  if (!el.selected) el.select(0);
  return el;
};

document.querySelectorAll('.ui-component-select').forEach(Select);

export default Select<commit_msg>fixed select behavior<commit_after>/*
 * select.js
 * @author Jared Smith <jasmith79@gmail.com>
 * @copyright 2017
 * @license MIT
 *
 * JavaScript file for ui-component-select
 */

import List from 'list';
import { CoreElementMixin, extractType } from 'core';

const SelectMixin = {
  // Overrides the List version
  select (opt) {
    if (extractType(opt) === 'Number') {
      let option = this.options[opt];
      if (option) {
        this.options.forEach(o => {
          o.removeAttribute('selected');
        });
        option.setAttribute('selected', true);
        this.selected = option;
      }

      return this.close();
    } else {
      return this.select(this.options.indexOf(opt));
    }
  },

  open () {
    this.removeAttribute('collapsed')
    return this;
  },

  close () {
    this.setAttribute('collapsed', true);
    return this;
  },

  selected: null
};

const Select = function Select(el=document.createElement('div')) {
  el.classList.add('ui-component-select');
  el.options = Array.from(el.querySelectorAll('.ui-component-option'));
  el.options.forEach((x, i) => {
    x.classList.add('ui-component-list-item');
    let arrow = document.createElement('div');
    arrow.classList.add('ui-component-down-arrow');
    x.appendChild(arrow);
    x.addEventListener('click', e => {
      if (!el.getAttribute('collapsed')) {
        el.select(i);
        setTimeout(_ => { el.close() }, 500);
      }
    });
  });

  let pendingClose = null;
  el.addEventListener('click', () => el.open());
  el.addEventListener('mouseleave', () => pendingClose = setTimeout(() => { el.close(); }, 700));
  el.addEventListener('mouseenter', () => clearTimeout(pendingClose));

  Object.assign(List(el), SelectMixin);
  if (!el.selected) el.select(0);
  return el;
};

document.querySelectorAll('.ui-component-select').forEach(Select);

Select.fromArray = ls => {

};

export default Select<|endoftext|>


<commit_before>const config = require('./config');
const sentryConfig = config.get('sentry');

const expressNoop = function (req, res, next) {
    next();
};

if (sentryConfig && !sentryConfig.disabled) {
    const Sentry = require('@sentry/node');
    const version = require('../../package.json').version;
    Sentry.init({
        dsn: sentryConfig.dsn,
        release: 'ghost@' + version
    });

    module.exports = {
        requestHandler: Sentry.Handlers.requestHandler(),
        errorHandler: Sentry.Handlers.errorHandler({
            shouldHandleError(error) {
                // Only handle 500 errors for now
                return (error.statusCode === 500);
            }
        })
    };
} else {
    module.exports = {
        requestHandler: expressNoop,
        errorHandler: expressNoop
    };
}
<commit_msg>Added captureException helper to Sentry integration<commit_after>const config = require('./config');
const sentryConfig = config.get('sentry');

const expressNoop = function (req, res, next) {
    next();
};

if (sentryConfig && !sentryConfig.disabled) {
    const Sentry = require('@sentry/node');
    const version = require('../../package.json').version;
    Sentry.init({
        dsn: sentryConfig.dsn,
        release: 'ghost@' + version
    });

    module.exports = {
        requestHandler: Sentry.Handlers.requestHandler(),
        errorHandler: Sentry.Handlers.errorHandler({
            shouldHandleError(error) {
                // Only handle 500 errors for now
                return (error.statusCode === 500);
            }
        }),
        captureException: Sentry.captureException
    };
} else {
    module.exports = {
        requestHandler: expressNoop,
        errorHandler: expressNoop,
        captureException: () => {}
    };
}
<|endoftext|>