<commit_before>/*eslint no-invalid-this: 0*/ // eslint doesn't understand Promise.coroutine wrapping
"use strict";
var Promise = require("bluebird");
var IrcRoom = require("../models/IrcRoom");
var IrcAction = require("../models/IrcAction");
var MatrixRoom = require("matrix-appservice-bridge").MatrixRoom;
var ConfigValidator = require("matrix-appservice-bridge").ConfigValidator;

var log = require("../logging").get("Provisioner");
var promiseutil = require("../promiseutil.js");

var matrixRoomIdValidation = {
    "type": "string",
    "pattern": "^!.*:.*$"
};

var validationProperties = {
    "matrix_room_id" : matrixRoomIdValidation,
    "remote_room_channel" : {
        "type": "string",
        "pattern": "^([#+&]|(![A-Z0-9]{5}))[^\\s:,]+$"
    },
    "remote_room_server" : {
        "type": "string",
        "pattern": "^[a-z\\.0-9:-]+$"
    },
    "op_nick" : {
        "type": "string"
    },
    "key" : {
        "type": "string"
    },
    "user_id" : {
        "type": "string"
    }
};

function Provisioner(ircBridge, enabled, requestTimeoutSeconds) {
    this._ircBridge = ircBridge;
    this._enabled = enabled;
    this._requestTimeoutSeconds = requestTimeoutSeconds;
    this._pendingRequests = {};
    // {
    //   $domain: {
    //     $nick: {
    //        userId : string
    //        defer: Deferred
    //     }
    //   }

    this._linkValidator = new ConfigValidator({
        "type": "object",
        "properties": validationProperties,
        "required": [
            "matrix_room_id",
            "remote_room_channel",
            "remote_room_server",
            "op_nick",
            "user_id"
        ]
    });
    this._unlinkValidator = new ConfigValidator({
        "type": "object",
        "properties": validationProperties,
        "required": [
            "matrix_room_id",
            "remote_room_channel",
            "remote_room_server"
        ]
    });
    this._roomIdValidator = new ConfigValidator({
        "type": "object",
        "properties": {
            "matrix_room_id" : matrixRoomIdValidation
        }
    });

    if (enabled) {
        log.info("Starting provisioning...");
    }
    else {
        log.info("Provisioning disabled.");
    }

    let as = this._ircBridge.getAppServiceBridge().appService;
    let self = this;

    if (enabled && !(as.app.use && as.app.get && as.app.post)) {
        throw new Error('Could not start provisioning API');
    }

    // Disable all provision endpoints by not calling 'next' and returning an error instead
    if (!enabled) {
        as.app.use(function(req, res, next) {
            if (self.isProvisionRequest(req)) {
                res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Headers",
                    "Origin, X-Requested-With, Content-Type, Accept");
                res.status(200);
                res.json({error : 'Provisioning is not enabled.'});
            }
            else {
                next();
            }
        });
    }

    // Deal with CORS (temporarily for s-web)
    as.app.use(function(req, res, next) {
        if (self.isProvisionRequest(req)) {
                res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Headers",
                    "Origin, X-Requested-With, Content-Type, Accept");
        }
        next();
    });

    as.app.post("/_matrix/provision/link", Promise.coroutine(function*(req, res) {
        try {
            yield self.requestLink(req.body);
            res.json({});
        }
        catch (err) {
            res.status(500).json({error: err.message});
            throw err;
        }
    }));

    as.app.post("/_matrix/provision/unlink", Promise.coroutine(function*(req, res) {
        try {
            yield self.unlink(req.body);
            res.json({});
        }
        catch (err) {
            res.status(500).json({error: err.message});
            throw err;
        }
    }));

    as.app.get("/_matrix/provision/listlinks/:roomId", Promise.coroutine(function*(req, res) {
        try {
            let list = yield self.listings(req.params.roomId);
            res.json(list);
        }
        catch (err) {
            res.status(500).json({error: err.message});
            throw err;
        }
    }));

    if (enabled) {
        log.info("Provisioning started");
    }
}

Provisioner.prototype.isProvisionRequest = function(req) {
    return req.url === '/_matrix/provision/unlink' ||
            req.url === '/_matrix/provision/link'||
            req.url.match(/^\/_matrix\/provision\/listlinks/)
};

Provisioner.prototype._updateBridgingState = Promise.coroutine(
    function*(roomId, userId, status, skey) {
        let intent = this._ircBridge.getAppServiceBridge().getIntent();
        try {
            yield intent.client.sendStateEvent(roomId, 'm.room.bridging', {
                user_id: userId,
                status: status // pending, success, failure
            }, skey);
        }
        catch (err) {
            console.error(err);
            throw new Error(`Could not update m.room.bridging state in this room`);
        }
    }
);

// Do a series of checks before contacting an operator for permission to create
//  a provisioned mapping. If the operator responds with 'yes' or 'y', the mapping
//  is created.
// The checks done are the following:
//  - (Matrix) Check power level of user is high enough
//  - (IRC) Check that op's nick is actually a channel op
//  - (Matrix) update room state m.room.brdiging
Provisioner.prototype._authoriseProvisioning = Promise.coroutine(
    function*(server, userId, ircChannel, roomId, opNick, key) {
        let ircDomain = server.domain;

        let existing = this._getRequest(server, opNick);
        if (existing) {
            let from = existing.userId;
            throw new Error(`Bridging request already sent to `+
                            `${opNick} on ${server.domain} from ${from}`);
        }

        // (Matrix) Check power level of user
        log.info(`Check power level of ${userId} in ${roomId}`);
        let matrixClient = this._ircBridge.getAppServiceBridge().getClientFactory().getClientAs();

        let powerState = null;
        try {
            powerState = yield matrixClient.getStateEvent(roomId, 'm.room.power_levels');
        }
        catch (err) {
            log.error(`Error retrieving power levels (${err.data.error})`);
        }

        if (!powerState) {
            throw new Error('Could not retrieve your power levels for the room');
        }

        let actualPower = 0;
        if (powerState.users[userId] !== undefined) {
            actualPower = powerState.users[userId];
        }
        else if (powerState.users_default !== undefined) {
            actualPower = powerState.users_default;
        }

        let requiredPower = 50;
        if (powerState.events["m.room.power_levels"] !== undefined) {
            requiredPower = powerState.events["m.room.power_levels"]
        }
        else if (powerState.state_default !== undefined) {
            requiredPower = powerState.state_default;
        }

        if (actualPower < requiredPower) {
            throw new Error('User does not possess high enough power level');
        }

        // (IRC) Check that op's nick is actually op
        log.info(`Check that op's nick is actually op`);
        let botClient = yield this._ircBridge.getBotClient(server);
        yield botClient.joinChannel(ircChannel, key);
        log.info(`Bot joined channel ${ircChannel}`);
        let info = yield botClient.getOperators(ircChannel);

        if (info.nicks.indexOf(opNick) === -1) {
            let knownOps = info.operatorNicks.join(', ');
            throw new Error(`Provided user is not in channel ${ircChannel}. ` +
                            `Known ops in this channel: ${knownOps}`);
        }

        if (info.operatorNicks.indexOf(opNick) === -1) {
            let knownOps = info.operatorNicks.join(', ');
            throw new Error(`Provided user is not an op of ${ircChannel}. ` +
                            `Known ops in this channel: ${knownOps}`);
        }

        // (Matrix) update room state
        log.info(`Sending m.room.bridging`);
        // State key for m.room.bridging
        let skey = `irc://${ircDomain}/${ircChannel}`;

        // Send pending m.room.bridging
        yield this._updateBridgingState(roomId, userId, 'pending', skey);

        // (IRC) Ask operator for authorisation
        // Time that operator has to respond before giving up
        let timeoutSeconds = this._requestTimeoutSeconds;

        // Deliberately not yielding on this so that 200 OK is returned
        log.info(`Contacting operator`);
        this._contactOperator(
            botClient, server, opNick, ircChannel,
            roomId, userId, skey, timeoutSeconds);
    }
);

// Contact an operator, asking for authorisation for a mapping, and if they reply
//  'yes' or 'y', create the mapping.
Provisioner.prototype._contactOperator = Promise.coroutine(
    function*(botClient, server, opNick, ircChannel, roomId, userId, skey, timeoutSeconds) {
        let d = promiseutil.defer();

        // Send PM to operator
        let authRequestAction = new IrcAction("message",
            `${userId} has requested to bridge ${roomId} with ${ircChannel} on this IRC ` +
            `network. Respond with 'yes' or 'y' to allow, or simply ignore this message to ` +
            `dissallow. You have ${timeoutSeconds} seconds from when this message was sent.`);
        let pmRoom = new IrcRoom(server, opNick);

        this._setRequest(server, opNick, {userId: userId, defer: d});

        this._ircBridge.sendIrcAction(pmRoom, botClient, authRequestAction);
        try {
            yield d.promise.timeout(timeoutSeconds * 1000);
            this._removeRequest(server, opNick);
        }
        catch (err) {
            log.info(`Operator ${opNick} did not respond (${err.message})`);
            yield this._updateBridgingState(roomId, userId, 'failure', skey);
            this._removeRequest(server, opNick);
            return;
        }
        try {
            yield this._doLink(server, ircChannel, roomId);
        }
        catch (err) {
            log.info(`Failed to create link following authorisation (${err.message})`);
            yield this._updateBridgingState(roomId, userId, 'failure', skey);
            this._removeRequest(server, opNick);
            return;
        }
        yield this._updateBridgingState(roomId, userId, 'success', skey);
    }
);

Provisioner.prototype._removeRequest = function (server, opNick) {
    delete this._pendingRequests[server.domain][opNick];
}

Provisioner.prototype._getRequest = function (server, opNick) {
    if (this._pendingRequests[server.domain]) {
        return this._pendingRequests[server.domain][opNick];
    }
}

Provisioner.prototype._setRequest = function (server, opNick, request) {
    if (!this._pendingRequests[server.domain]) {
        this._pendingRequests[server.domain] = {};
    }
    this._pendingRequests[server.domain][opNick] = request;
}

Provisioner.prototype.handlePm = function(server, fromUser, text) {
    if (['y', 'yes'].indexOf(text.trim().toLowerCase()) == -1) {
        log.warn(`Provisioner only handles text 'yes'/'y' ` +
                 `(from ${fromUser.nick} on ${server.domain})`);
        return;
    }
    let request = this._getRequest(server, fromUser.nick);
    if (request) {
        log.info(`${fromUser.nick} has authorised a new provisioning`);
        request.defer.resolve();
        return;
    }
    log.warn(`Provisioner was not expecting PM from ${fromUser.nick} on ${server.domain}`);
}

// Link an IRC channel to a matrix room ID
Provisioner.prototype.requestLink = Promise.coroutine(function*(options) {
    try {
        this._linkValidator.validate(options);
    }
    catch (err) {
        log.error(err);
        throw new Error("Malformed parameters");
    }

    let ircDomain = options.remote_room_server;
    let ircChannel = options.remote_room_channel;
    let roomId = options.matrix_room_id;
    let opNick = options.op_nick;
    let key = options.key || undefined; // Optional key
    let userId = options.user_id;
    let mappingLogId = `${roomId} <---> ${ircDomain}/${ircChannel}`;

    // Try to find the domain requested for linking
    //TODO: ircDomain might include protocol, i.e. irc://irc.freenode.net
    let server = this._ircBridge.getServer(ircDomain);

    if (!server) {
        throw new Error(`Server requested for linking not found ('${ircDomain}')`);
    }

    if (server.isExcludedChannel(ircChannel)) {
        throw new Error(`Server is configured to exclude given channel ('${ircChannel}')`);
    }

    let entry = yield this._ircBridge.getStore().getRoom(roomId, ircDomain, ircChannel);
    if (!entry) {
        // Ask OP for provisioning authentication
        try {
            yield this._authoriseProvisioning(server, userId, ircChannel, roomId, opNick, key);
        }
        catch (err) {
            console.error(err.stack);
            throw new Error('Failed to authorise provisioning');
        }
    }
    else {
        throw new Error(`Room mapping already exists (${mappingLogId},` +
                        `origin = ${entry.data.origin})`);
    }
});

Provisioner.prototype._doLink = Promise.coroutine(
    function*(server, ircChannel, roomId) {
        let ircDomain = server.domain;
        let mappingLogId = `${roomId} <---> ${ircDomain}/${ircChannel}`;
        log.info(`Provisioning link for room ${mappingLogId}`);

        // Create rooms for the link
        let ircRoom = new IrcRoom(server, ircChannel);
        let mxRoom = new MatrixRoom(roomId);

        let entry = yield this._ircBridge.getStore().getRoom(roomId, ircDomain, ircChannel);
        if (!entry) {
            yield this._ircBridge.getStore().storeRoom(ircRoom, mxRoom, 'provision');
        }
        else {
            throw new Error(`Room mapping already exists (${mappingLogId},` +
                            `origin = ${entry.data.origin})`);
        }
    }
);

// Unlink an IRC channel from a matrix room ID
Provisioner.prototype.unlink = Promise.coroutine(function*(options) {
    try {
        this._unlinkValidator.validate(options);
    }
    catch (err) {
        log.error(err);
        throw new Error("Malformed parameters");
    }

    let ircDomain = options.remote_room_server;
    let ircChannel = options.remote_room_channel;
    let roomId = options.matrix_room_id;
    let mappingLogId = `${roomId} <-/-> ${ircDomain}/${ircChannel}`;

    log.info(`Provisioning unlink for room ${mappingLogId}`);

    // Try to find the domain requested for unlinking
    let server = this._ircBridge.getServer(ircDomain);

    if (!server) {
        throw new Error("Server requested for linking not found");
    }

    // Delete the room link
    let entry = yield this._ircBridge.getStore()
        .getRoom(roomId, ircDomain, ircChannel, 'provision');

    if (entry) {
        yield this._ircBridge.getStore().removeRoom(roomId, ircDomain, ircChannel, 'provision');
    }
    else {
        throw new Error(`Provisioned room mapping does not exist (${mappingLogId})`);
    }
});

// List all mappings currently provisioned with the given matrix_room_id
Provisioner.prototype.listings = function(roomId) {
    try {
        this._roomIdValidator.validate({"matrix_room_id": roomId});
    }
    catch (err) {
        log.error(err);
        throw new Error("Malformed parameters");
    }

    return this._ircBridge.getStore()
        .getProvisionedMappings(roomId)
        .map((entry) => {
            return {
                matrix_room_id : entry.matrix.roomId,
                remote_room_channel : entry.remote.data.channel,
                remote_room_server : entry.remote.data.domain,
            }
        });
};

module.exports = Provisioner;
<commit_msg>Better logging<commit_after>/*eslint no-invalid-this: 0*/ // eslint doesn't understand Promise.coroutine wrapping
"use strict";
var Promise = require("bluebird");
var IrcRoom = require("../models/IrcRoom");
var IrcAction = require("../models/IrcAction");
var MatrixRoom = require("matrix-appservice-bridge").MatrixRoom;
var ConfigValidator = require("matrix-appservice-bridge").ConfigValidator;

var log = require("../logging").get("Provisioner");
var promiseutil = require("../promiseutil.js");

var matrixRoomIdValidation = {
    "type": "string",
    "pattern": "^!.*:.*$"
};

var validationProperties = {
    "matrix_room_id" : matrixRoomIdValidation,
    "remote_room_channel" : {
        "type": "string",
        "pattern": "^([#+&]|(![A-Z0-9]{5}))[^\\s:,]+$"
    },
    "remote_room_server" : {
        "type": "string",
        "pattern": "^[a-z\\.0-9:-]+$"
    },
    "op_nick" : {
        "type": "string"
    },
    "key" : {
        "type": "string"
    },
    "user_id" : {
        "type": "string"
    }
};

function Provisioner(ircBridge, enabled, requestTimeoutSeconds) {
    this._ircBridge = ircBridge;
    this._enabled = enabled;
    this._requestTimeoutSeconds = requestTimeoutSeconds;
    this._pendingRequests = {};
    // {
    //   $domain: {
    //     $nick: {
    //        userId : string
    //        defer: Deferred
    //     }
    //   }

    this._linkValidator = new ConfigValidator({
        "type": "object",
        "properties": validationProperties,
        "required": [
            "matrix_room_id",
            "remote_room_channel",
            "remote_room_server",
            "op_nick",
            "user_id"
        ]
    });
    this._unlinkValidator = new ConfigValidator({
        "type": "object",
        "properties": validationProperties,
        "required": [
            "matrix_room_id",
            "remote_room_channel",
            "remote_room_server"
        ]
    });
    this._roomIdValidator = new ConfigValidator({
        "type": "object",
        "properties": {
            "matrix_room_id" : matrixRoomIdValidation
        }
    });

    if (enabled) {
        log.info("Starting provisioning...");
    }
    else {
        log.info("Provisioning disabled.");
    }

    let as = this._ircBridge.getAppServiceBridge().appService;
    let self = this;

    if (enabled && !(as.app.use && as.app.get && as.app.post)) {
        throw new Error('Could not start provisioning API');
    }

    // Disable all provision endpoints by not calling 'next' and returning an error instead
    if (!enabled) {
        as.app.use(function(req, res, next) {
            if (self.isProvisionRequest(req)) {
                res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Headers",
                    "Origin, X-Requested-With, Content-Type, Accept");
                res.status(200);
                res.json({error : 'Provisioning is not enabled.'});
            }
            else {
                next();
            }
        });
    }

    // Deal with CORS (temporarily for s-web)
    as.app.use(function(req, res, next) {
        if (self.isProvisionRequest(req)) {
                res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Headers",
                    "Origin, X-Requested-With, Content-Type, Accept");
        }
        next();
    });

    as.app.post("/_matrix/provision/link", Promise.coroutine(function*(req, res) {
        try {
            yield self.requestLink(req.body);
            res.json({});
        }
        catch (err) {
            res.status(500).json({error: err.message});
            throw err;
        }
    }));

    as.app.post("/_matrix/provision/unlink", Promise.coroutine(function*(req, res) {
        try {
            yield self.unlink(req.body);
            res.json({});
        }
        catch (err) {
            res.status(500).json({error: err.message});
            throw err;
        }
    }));

    as.app.get("/_matrix/provision/listlinks/:roomId", Promise.coroutine(function*(req, res) {
        try {
            let list = yield self.listings(req.params.roomId);
            res.json(list);
        }
        catch (err) {
            res.status(500).json({error: err.message});
            throw err;
        }
    }));

    if (enabled) {
        log.info("Provisioning started");
    }
}

Provisioner.prototype.isProvisionRequest = function(req) {
    return req.url === '/_matrix/provision/unlink' ||
            req.url === '/_matrix/provision/link'||
            req.url.match(/^\/_matrix\/provision\/listlinks/)
};

Provisioner.prototype._updateBridgingState = Promise.coroutine(
    function*(roomId, userId, status, skey) {
        let intent = this._ircBridge.getAppServiceBridge().getIntent();
        try {
            yield intent.client.sendStateEvent(roomId, 'm.room.bridging', {
                user_id: userId,
                status: status // pending, success, failure
            }, skey);
        }
        catch (err) {
            console.error(err);
            throw new Error(`Could not update m.room.bridging state in this room`);
        }
    }
);

// Do a series of checks before contacting an operator for permission to create
//  a provisioned mapping. If the operator responds with 'yes' or 'y', the mapping
//  is created.
// The checks done are the following:
//  - (Matrix) Check power level of user is high enough
//  - (IRC) Check that op's nick is actually a channel op
//  - (Matrix) update room state m.room.brdiging
Provisioner.prototype._authoriseProvisioning = Promise.coroutine(
    function*(server, userId, ircChannel, roomId, opNick, key) {
        let ircDomain = server.domain;

        let existing = this._getRequest(server, opNick);
        if (existing) {
            let from = existing.userId;
            throw new Error(`Bridging request already sent to `+
                            `${opNick} on ${server.domain} from ${from}`);
        }

        // (Matrix) Check power level of user
        log.info(`Check power level of ${userId} in ${roomId}`);
        let matrixClient = this._ircBridge.getAppServiceBridge().getClientFactory().getClientAs();

        let powerState = null;
        try {
            powerState = yield matrixClient.getStateEvent(roomId, 'm.room.power_levels');
        }
        catch (err) {
            log.error(`Error retrieving power levels (${err.data.error})`);
        }

        if (!powerState) {
            throw new Error('Could not retrieve your power levels for the room');
        }

        let actualPower = 0;
        if (powerState.users[userId] !== undefined) {
            actualPower = powerState.users[userId];
        }
        else if (powerState.users_default !== undefined) {
            actualPower = powerState.users_default;
        }

        let requiredPower = 50;
        if (powerState.events["m.room.power_levels"] !== undefined) {
            requiredPower = powerState.events["m.room.power_levels"]
        }
        else if (powerState.state_default !== undefined) {
            requiredPower = powerState.state_default;
        }

        if (actualPower < requiredPower) {
            throw new Error('User does not possess high enough power level');
        }

        // (IRC) Check that op's nick is actually op
        log.info(`Check that op's nick is actually op`);
        let botClient = yield this._ircBridge.getBotClient(server);
        yield botClient.joinChannel(ircChannel, key);
        log.info(`Bot joined channel ${ircChannel}`);
        let info = yield botClient.getOperators(ircChannel);

        if (info.nicks.indexOf(opNick) === -1) {
            let knownOps = info.operatorNicks.join(', ');
            throw new Error(`Provided user is not in channel ${ircChannel}. ` +
                            `Known ops in this channel: ${knownOps}`);
        }

        if (info.operatorNicks.indexOf(opNick) === -1) {
            let knownOps = info.operatorNicks.join(', ');
            throw new Error(`Provided user is not an op of ${ircChannel}. ` +
                            `Known ops in this channel: ${knownOps}`);
        }

        // (Matrix) update room state
        // State key for m.room.bridging
        let skey = `irc://${ircDomain}/${ircChannel}`;
        log.info(`Sending m.room.bridging to ${roomId}, state key = ${skey}`);

        // Send pending m.room.bridging
        yield this._updateBridgingState(roomId, userId, 'pending', skey);

        // (IRC) Ask operator for authorisation
        // Time that operator has to respond before giving up
        let timeoutSeconds = this._requestTimeoutSeconds;

        // Deliberately not yielding on this so that 200 OK is returned
        log.info(`Contacting operator`);
        this._contactOperator(
            botClient, server, opNick, ircChannel,
            roomId, userId, skey, timeoutSeconds);
    }
);

// Contact an operator, asking for authorisation for a mapping, and if they reply
//  'yes' or 'y', create the mapping.
Provisioner.prototype._contactOperator = Promise.coroutine(
    function*(botClient, server, opNick, ircChannel, roomId, userId, skey, timeoutSeconds) {
        let d = promiseutil.defer();

        // Send PM to operator
        let authRequestAction = new IrcAction("message",
            `${userId} has requested to bridge ${roomId} with ${ircChannel} on this IRC ` +
            `network. Respond with 'yes' or 'y' to allow, or simply ignore this message to ` +
            `dissallow. You have ${timeoutSeconds} seconds from when this message was sent.`);
        let pmRoom = new IrcRoom(server, opNick);

        this._setRequest(server, opNick, {userId: userId, defer: d});

        this._ircBridge.sendIrcAction(pmRoom, botClient, authRequestAction);
        try {
            yield d.promise.timeout(timeoutSeconds * 1000);
            this._removeRequest(server, opNick);
        }
        catch (err) {
            log.info(`Operator ${opNick} did not respond (${err.message})`);
            yield this._updateBridgingState(roomId, userId, 'failure', skey);
            this._removeRequest(server, opNick);
            return;
        }
        try {
            yield this._doLink(server, ircChannel, roomId);
        }
        catch (err) {
            log.info(`Failed to create link following authorisation (${err.message})`);
            yield this._updateBridgingState(roomId, userId, 'failure', skey);
            this._removeRequest(server, opNick);
            return;
        }
        yield this._updateBridgingState(roomId, userId, 'success', skey);
    }
);

Provisioner.prototype._removeRequest = function (server, opNick) {
    delete this._pendingRequests[server.domain][opNick];
}

Provisioner.prototype._getRequest = function (server, opNick) {
    if (this._pendingRequests[server.domain]) {
        return this._pendingRequests[server.domain][opNick];
    }
}

Provisioner.prototype._setRequest = function (server, opNick, request) {
    if (!this._pendingRequests[server.domain]) {
        this._pendingRequests[server.domain] = {};
    }
    this._pendingRequests[server.domain][opNick] = request;
}

Provisioner.prototype.handlePm = function(server, fromUser, text) {
    if (['y', 'yes'].indexOf(text.trim().toLowerCase()) == -1) {
        log.warn(`Provisioner only handles text 'yes'/'y' ` +
                 `(from ${fromUser.nick} on ${server.domain})`);
        return;
    }
    let request = this._getRequest(server, fromUser.nick);
    if (request) {
        log.info(`${fromUser.nick} has authorised a new provisioning`);
        request.defer.resolve();
        return;
    }
    log.warn(`Provisioner was not expecting PM from ${fromUser.nick} on ${server.domain}`);
}

// Link an IRC channel to a matrix room ID
Provisioner.prototype.requestLink = Promise.coroutine(function*(options) {
    try {
        this._linkValidator.validate(options);
    }
    catch (err) {
        log.error(err);
        throw new Error("Malformed parameters");
    }

    let ircDomain = options.remote_room_server;
    let ircChannel = options.remote_room_channel;
    let roomId = options.matrix_room_id;
    let opNick = options.op_nick;
    let key = options.key || undefined; // Optional key
    let userId = options.user_id;
    let mappingLogId = `${roomId} <---> ${ircDomain}/${ircChannel}`;

    // Try to find the domain requested for linking
    //TODO: ircDomain might include protocol, i.e. irc://irc.freenode.net
    let server = this._ircBridge.getServer(ircDomain);

    if (!server) {
        throw new Error(`Server requested for linking not found ('${ircDomain}')`);
    }

    if (server.isExcludedChannel(ircChannel)) {
        throw new Error(`Server is configured to exclude given channel ('${ircChannel}')`);
    }

    let entry = yield this._ircBridge.getStore().getRoom(roomId, ircDomain, ircChannel);
    if (!entry) {
        // Ask OP for provisioning authentication
        try {
            yield this._authoriseProvisioning(server, userId, ircChannel, roomId, opNick, key);
        }
        catch (err) {
            console.error(err.stack);
            throw new Error('Failed to authorise provisioning');
        }
    }
    else {
        throw new Error(`Room mapping already exists (${mappingLogId},` +
                        `origin = ${entry.data.origin})`);
    }
});

Provisioner.prototype._doLink = Promise.coroutine(
    function*(server, ircChannel, roomId) {
        let ircDomain = server.domain;
        let mappingLogId = `${roomId} <---> ${ircDomain}/${ircChannel}`;
        log.info(`Provisioning link for room ${mappingLogId}`);

        // Create rooms for the link
        let ircRoom = new IrcRoom(server, ircChannel);
        let mxRoom = new MatrixRoom(roomId);

        let entry = yield this._ircBridge.getStore().getRoom(roomId, ircDomain, ircChannel);
        if (!entry) {
            yield this._ircBridge.getStore().storeRoom(ircRoom, mxRoom, 'provision');
        }
        else {
            throw new Error(`Room mapping already exists (${mappingLogId},` +
                            `origin = ${entry.data.origin})`);
        }
    }
);

// Unlink an IRC channel from a matrix room ID
Provisioner.prototype.unlink = Promise.coroutine(function*(options) {
    try {
        this._unlinkValidator.validate(options);
    }
    catch (err) {
        log.error(err);
        throw new Error("Malformed parameters");
    }

    let ircDomain = options.remote_room_server;
    let ircChannel = options.remote_room_channel;
    let roomId = options.matrix_room_id;
    let mappingLogId = `${roomId} <-/-> ${ircDomain}/${ircChannel}`;

    log.info(`Provisioning unlink for room ${mappingLogId}`);

    // Try to find the domain requested for unlinking
    let server = this._ircBridge.getServer(ircDomain);

    if (!server) {
        throw new Error("Server requested for linking not found");
    }

    // Delete the room link
    let entry = yield this._ircBridge.getStore()
        .getRoom(roomId, ircDomain, ircChannel, 'provision');

    if (entry) {
        yield this._ircBridge.getStore().removeRoom(roomId, ircDomain, ircChannel, 'provision');
    }
    else {
        throw new Error(`Provisioned room mapping does not exist (${mappingLogId})`);
    }
});

// List all mappings currently provisioned with the given matrix_room_id
Provisioner.prototype.listings = function(roomId) {
    try {
        this._roomIdValidator.validate({"matrix_room_id": roomId});
    }
    catch (err) {
        log.error(err);
        throw new Error("Malformed parameters");
    }

    return this._ircBridge.getStore()
        .getProvisionedMappings(roomId)
        .map((entry) => {
            return {
                matrix_room_id : entry.matrix.roomId,
                remote_room_channel : entry.remote.data.channel,
                remote_room_server : entry.remote.data.domain,
            }
        });
};

module.exports = Provisioner;
<|endoftext|>


<commit_before><commit_msg>Add getDropletContents helper for UI test<commit_after><|endoftext|>


<commit_before>/* eslint-disable space-infix-ops */
import { max } from "d3-array";
import { timerStart, timerEnd } from "../../../util/perf";

export const hideGrid = function hideGrid() {
  this.svg.selectAll(".majorGrid").style('visibility', 'hidden');
  this.svg.selectAll(".minorGrid").style('visibility', 'hidden');
  this.svg.selectAll(".gridTick").style('visibility', 'hidden');
};

/**
 * add a grid to the svg
 * @param {layout}
 */
export const addGrid = function addGrid(layout, yMinView, yMaxView) {
  timerStart("addGrid");
  if (typeof layout==="undefined") {layout=this.layout;} // eslint-disable-line no-param-reassign

  const xmin = (this.xScale.domain()[0]>0)?this.xScale.domain()[0]:0.0;
  const ymin = this.yScale.domain()[1];
  const ymax = this.yScale.domain()[0];
  const xmax = layout === "radial" ?
    max([this.xScale.domain()[1], this.yScale.domain()[1], -this.xScale.domain()[0], -this.yScale.domain()[0]]) :
    this.xScale.domain()[1];

  const offset = layout==="radial"?this.nodes[0].depth:0.0;
  const viewTop = yMaxView ? yMaxView+this.params.margins.top : this.yScale.range()[0];
  const viewBottom = yMinView ? yMinView-this.params.margins.bottom : this.yScale.range()[1];

  /* should we re-draw the grid? */
  if (!this.gridParams) {
    this.gridParams = [xmin, xmax, ymin, ymax, viewTop, viewBottom, layout];
  } else if (xmin === this.gridParams[0] && xmax === this.gridParams[1] &&
        ymin === this.gridParams[2] && ymax === this.gridParams[3] &&
        viewTop === this.gridParams[4] && viewBottom === this.gridParams[5] &&
        layout === this.gridParams[6]) {
    // console.log("bailing - no difference");
    return;
  }

  /* yes - redraw and update gridParams */
  this.gridParams = [xmin, xmax, ymin, ymax, viewTop, viewBottom, layout];

  const gridline = function gridline(xScale, yScale, layoutShadow) {
    return (x) => {
      const xPos = xScale(x[0]-offset);
      let tmp_d="";
      if (layoutShadow==="rect" || layoutShadow==="clock") {
        tmp_d = 'M'+xPos.toString() +
          " " +
          viewBottom.toString() +
          " L " +
          xPos.toString() +
          " " +
          viewTop.toString();
      } else if (layoutShadow==="radial") {
        tmp_d = 'M '+xPos.toString() +
          "  " +
          yScale(0).toString() +
          " A " +
          (xPos - xScale(0)).toString() +
          " " +
          (yScale(x[0]) - yScale(offset)).toString() +
          " 0 1 0 " +
          xPos.toString() +
          " " +
          (yScale(0)+0.001).toString();
      }
      return tmp_d;
    };
  };

  const logRange = Math.floor(Math.log10(xmax - xmin));
  const roundingLevel = Math.pow(10, logRange); // eslint-disable-line no-restricted-properties
  const gridMin = Math.floor((xmin+offset)/roundingLevel)*roundingLevel;
  const gridPoints = [];
  for (let ii = 0; ii <= (xmax + offset - gridMin)/roundingLevel+10; ii++) {
    const pos = gridMin + roundingLevel*ii;
    if (pos>offset) {
      gridPoints.push([pos, pos-offset>xmax?"hidden":"visible", "x"]);
    }
  }

  const majorGrid = this.svg.selectAll('.majorGrid').data(gridPoints);

  majorGrid.exit().remove(); // EXIT
  majorGrid.enter().append("path") // ENTER
    .merge(majorGrid) // ENTER + UPDATE
    .attr("d", gridline(this.xScale, this.yScale, layout))
    .attr("class", "majorGrid")
    .style("fill", "none")
    .style("visibility", (d) => d[1])
    .style("stroke", this.params.majorGridStroke)
    .style("stroke-width", this.params.majorGridWidth);

  const xTextPos = (xScale, layoutShadow) => (x) => {
    if (x[2] === "x") {
      return layoutShadow === "radial" ? xScale(0) : xScale(x[0]);
    }
    return xScale.range()[1];
  };
  const yTextPos = (yScale, layoutShadow) => (x) => {
    if (x[2] === "x") {
      return layoutShadow === "radial" ? yScale(x[0]-offset) : viewBottom + 18;
    }
    return yScale(x[0]);
  };

  let logRangeY = 0;
  if (this.layout==="clock") {
    const roundingLevelY = Math.pow(10, logRangeY); // eslint-disable-line no-restricted-properties
    logRangeY = Math.floor(Math.log10(ymax - ymin));
    const offsetY=0;
    const gridMinY = Math.floor((ymin+offsetY)/roundingLevelY)*roundingLevelY;
    for (let ii = 0; ii <= (ymax + offsetY - gridMinY)/roundingLevelY+10; ii++) {
      const pos = gridMinY + roundingLevelY*ii;
      if (pos>offsetY) {
        gridPoints.push([pos, pos-offsetY>ymax ? "hidden" : "visible", "y"]);
      }
    }
  }

  const minorRoundingLevel = roundingLevel /
    (this.distanceMeasure === "num_date"? this.params.minorTicksTimeTree : this.params.minorTicks);
  const minorGridPoints = [];
  for (let ii = 0; ii <= (xmax + offset - gridMin)/minorRoundingLevel+50; ii++) {
    const pos = gridMin + minorRoundingLevel*ii;
    if (pos>offset) {
      minorGridPoints.push([pos, pos-offset>xmax+minorRoundingLevel?"hidden":"visible"]);
    }
  }
  const minorGrid = this.svg.selectAll('.minorGrid').data(minorGridPoints);
  minorGrid.exit().remove(); // EXIT
  minorGrid.enter().append("path") // ENTER
    .merge(minorGrid) // ENTER + UPDATE
    .attr("d", gridline(this.xScale, this.yScale, layout))
    .attr("class", "minorGrid")
    .style("fill", "none")
    .style("visibility", (d) => d[1])
    .style("stroke", this.params.minorGridStroke)
    .style("stroke-width", this.params.minorGridWidth);

  const gridLabels = this.svg.selectAll('.gridTick').data(gridPoints);
  const precision = Math.max(0, 1-logRange);
  const precisionY = Math.max(0, 1-logRangeY);
  gridLabels.exit().remove(); // EXIT
  gridLabels.enter().append("text") // ENTER
    .merge(gridLabels) // ENTER + UPDATE
    .text((d) => d[0].toFixed(d[2]==='y' ? precisionY : precision))
    .attr("class", "gridTick")
    .style("font-size", this.params.tickLabelSize)
    .style("font-family", this.params.fontFamily)
    .style("fill", this.params.tickLabelFill)
    .style("text-anchor", this.layout==="radial" ? "end" : "middle")
    .style("visibility", (d) => d[1])
    .attr("x", xTextPos(this.xScale, layout))
    .attr("y", yTextPos(this.yScale, layout));

  this.grid=true;
  timerEnd("addGrid");
};
<commit_msg>fix bug in phylotree axis where it would sometimes fail to redraw<commit_after>/* eslint-disable space-infix-ops */
import { max } from "d3-array";
import { timerStart, timerEnd } from "../../../util/perf";

export const hideGrid = function hideGrid() {
  this.svg.selectAll(".majorGrid").style('visibility', 'hidden');
  this.svg.selectAll(".minorGrid").style('visibility', 'hidden');
  this.svg.selectAll(".gridTick").style('visibility', 'hidden');
};

/**
 * add a grid to the svg
 * @param {layout}
 */
export const addGrid = function addGrid(layout, yMinView, yMaxView) {
  timerStart("addGrid");
  if (typeof layout==="undefined") {layout=this.layout;} // eslint-disable-line no-param-reassign

  const xmin = (this.xScale.domain()[0]>0)?this.xScale.domain()[0]:0.0;
  const ymin = this.yScale.domain()[1];
  const ymax = this.yScale.domain()[0];
  const xmax = layout === "radial" ?
    max([this.xScale.domain()[1], this.yScale.domain()[1], -this.xScale.domain()[0], -this.yScale.domain()[0]]) :
    this.xScale.domain()[1];

  const offset = layout==="radial"?this.nodes[0].depth:0.0;
  const viewTop = yMaxView ? yMaxView+this.params.margins.top : this.yScale.range()[0];
  const viewBottom = yMinView ? yMinView-this.params.margins.bottom : this.yScale.range()[1];

  /* should we re-draw the grid? */
  /* not running this block as it failed when the broswer dimensions had changed
  if (!this.gridParams) {
    this.gridParams = [xmin, xmax, ymin, ymax, viewTop, viewBottom, layout];
  } else if (
    xmin === this.gridParams[0] && xmax === this.gridParams[1] &&
    ymin === this.gridParams[2] && ymax === this.gridParams[3] &&
    viewTop === this.gridParams[4] && viewBottom === this.gridParams[5] &&
    layout === this.gridParams[6]
  ) {
    console.log("bailing - no difference");
    return;
  }
  */

  /* yes - redraw and update gridParams */
  this.gridParams = [xmin, xmax, ymin, ymax, viewTop, viewBottom, layout];

  const gridline = function gridline(xScale, yScale, layoutShadow) {
    return (x) => {
      const xPos = xScale(x[0]-offset);
      let tmp_d="";
      if (layoutShadow==="rect" || layoutShadow==="clock") {
        tmp_d = 'M'+xPos.toString() +
          " " +
          viewBottom.toString() +
          " L " +
          xPos.toString() +
          " " +
          viewTop.toString();
      } else if (layoutShadow==="radial") {
        tmp_d = 'M '+xPos.toString() +
          "  " +
          yScale(0).toString() +
          " A " +
          (xPos - xScale(0)).toString() +
          " " +
          (yScale(x[0]) - yScale(offset)).toString() +
          " 0 1 0 " +
          xPos.toString() +
          " " +
          (yScale(0)+0.001).toString();
      }
      return tmp_d;
    };
  };

  const logRange = Math.floor(Math.log10(xmax - xmin));
  const roundingLevel = Math.pow(10, logRange); // eslint-disable-line no-restricted-properties
  const gridMin = Math.floor((xmin+offset)/roundingLevel)*roundingLevel;
  const gridPoints = [];
  for (let ii = 0; ii <= (xmax + offset - gridMin)/roundingLevel+10; ii++) {
    const pos = gridMin + roundingLevel*ii;
    if (pos>offset) {
      gridPoints.push([pos, pos-offset>xmax?"hidden":"visible", "x"]);
    }
  }

  const majorGrid = this.svg.selectAll('.majorGrid').data(gridPoints);

  majorGrid.exit().remove(); // EXIT
  majorGrid.enter().append("path") // ENTER
    .merge(majorGrid) // ENTER + UPDATE
    .attr("d", gridline(this.xScale, this.yScale, layout))
    .attr("class", "majorGrid")
    .style("fill", "none")
    .style("visibility", (d) => d[1])
    .style("stroke", this.params.majorGridStroke)
    .style("stroke-width", this.params.majorGridWidth);

  const xTextPos = (xScale, layoutShadow) => (x) => {
    if (x[2] === "x") {
      return layoutShadow === "radial" ? xScale(0) : xScale(x[0]);
    }
    return xScale.range()[1];
  };
  const yTextPos = (yScale, layoutShadow) => (x) => {
    if (x[2] === "x") {
      return layoutShadow === "radial" ? yScale(x[0]-offset) : viewBottom + 18;
    }
    return yScale(x[0]);
  };

  let logRangeY = 0;
  if (this.layout==="clock") {
    const roundingLevelY = Math.pow(10, logRangeY); // eslint-disable-line no-restricted-properties
    logRangeY = Math.floor(Math.log10(ymax - ymin));
    const offsetY=0;
    const gridMinY = Math.floor((ymin+offsetY)/roundingLevelY)*roundingLevelY;
    for (let ii = 0; ii <= (ymax + offsetY - gridMinY)/roundingLevelY+10; ii++) {
      const pos = gridMinY + roundingLevelY*ii;
      if (pos>offsetY) {
        gridPoints.push([pos, pos-offsetY>ymax ? "hidden" : "visible", "y"]);
      }
    }
  }

  const minorRoundingLevel = roundingLevel /
    (this.distanceMeasure === "num_date"? this.params.minorTicksTimeTree : this.params.minorTicks);
  const minorGridPoints = [];
  for (let ii = 0; ii <= (xmax + offset - gridMin)/minorRoundingLevel+50; ii++) {
    const pos = gridMin + minorRoundingLevel*ii;
    if (pos>offset) {
      minorGridPoints.push([pos, pos-offset>xmax+minorRoundingLevel?"hidden":"visible"]);
    }
  }
  const minorGrid = this.svg.selectAll('.minorGrid').data(minorGridPoints);
  minorGrid.exit().remove(); // EXIT
  minorGrid.enter().append("path") // ENTER
    .merge(minorGrid) // ENTER + UPDATE
    .attr("d", gridline(this.xScale, this.yScale, layout))
    .attr("class", "minorGrid")
    .style("fill", "none")
    .style("visibility", (d) => d[1])
    .style("stroke", this.params.minorGridStroke)
    .style("stroke-width", this.params.minorGridWidth);

  const gridLabels = this.svg.selectAll('.gridTick').data(gridPoints);
  const precision = Math.max(0, 1-logRange);
  const precisionY = Math.max(0, 1-logRangeY);
  gridLabels.exit().remove(); // EXIT
  gridLabels.enter().append("text") // ENTER
    .merge(gridLabels) // ENTER + UPDATE
    .text((d) => d[0].toFixed(d[2]==='y' ? precisionY : precision))
    .attr("class", "gridTick")
    .style("font-size", this.params.tickLabelSize)
    .style("font-family", this.params.fontFamily)
    .style("fill", this.params.tickLabelFill)
    .style("text-anchor", this.layout==="radial" ? "end" : "middle")
    .style("visibility", (d) => d[1])
    .attr("x", xTextPos(this.xScale, layout))
    .attr("y", yTextPos(this.yScale, layout));

  this.grid=true;
  timerEnd("addGrid");
};
<|endoftext|>


<commit_before>/**
 * Component for drawing and editing of vector data.
 * Uses popup, measure.
 * Screen position of buttons determined by css in html file.
 */

// import templates
var tpl = require('components/draw.html');
var Component = require('components/component');
var component = new Component(tpl, 'draw');

var ol = require('ol');
var map = require('olMap').get();
var utils = require('utils');
var $ = utils.$;
// make sure toolbar initialised
require('components/toolbar');

// add drawtemplate to map
// use ol.control to prevent event propagation
map.addControl(new ol.control.Control({element: component.getTemplate('draw')}));

var select = require('select');
var vectors = require('vectors');
var measure = require('measure');
var overlay = require('components/popup').getOverlay();

var joinKey;
// FIXME English
var DRAWID = 'Drawn';
// FIXME should be configurable
var DECIMALS = 6; // coordinate decimal places, used by save

// add a vector layer/source
// FIXME enable drawing on existing layers
vectors.add({vectors: [{id: DRAWID}]});
var source = vectors.getLayers().item(vectors.getLayers().getLength() - 1)
    .getSource();

// create and add draw/modify interactions
var features = select.get().getFeatures();
var interactions = {
  point: new ol.interaction.Draw({
    source: source,
    type: 'Point'
  }),
  ls: new ol.interaction.Draw({
    source: source,
    type: 'LineString'
  }),
  poly: new ol.interaction.Draw({
    source: source,
    type: 'Polygon'
  }),
  modify: new ol.interaction.Modify({
    features: features
  }),
  split: new ol.interaction.Modify({
    features: features,
    deleteCondition: ol.events.condition.never
  })
};

var listener;
var form = component.getTemplate('featureform');

function plusFunc() {
  var temp = this.parentNode.querySelector('template');
  var clone = temp.content.firstElementChild.cloneNode(true);
  $('#featureformAtts').insertBefore(clone, temp);
  var i = $('#featureformAtts').querySelectorAll('input');
  i[i.length - 2].focus(); // focus on 1st new input field
}

var endCallback = function(e) {
  var el = overlay.getElement();
  // may be featuredisplay present
  while (el.firstChild) {
    el.removeChild(el.firstChild);
  }
  el.appendChild(form.cloneNode(true));
  // initialise displayed atts
  $('#featureformid').value = '';
  $('#plusbutton').onclick = plusFunc;
  // use onclick rather than addEventListener so only 1 active
  $('#featurebutton').onclick = function() {
    if ($('#featureformid').value) {
      e.feature.setId($('#featureformid').value);
    }
    var divs = $('#featureformAtts').querySelectorAll('div');
    for (var i = 0; i < divs.length; i++) {
      var inputs = divs[i].querySelectorAll('input');
      // only set attribute if there is a key
      if (inputs[0].value) {
        e.feature.set(inputs[0].value, inputs[1].value);
      }
    }
    el.style.display = 'none';
  };
  overlay.setPosition(map.getView().getCenter());
  el.style.display = 'block';
  $('#featureformid').focus();
  if (listener) {
    ol.Observable.unByKey(listener);
  }
};

interactions.point.on('drawend', endCallback);
interactions.ls.on('drawend', endCallback);
interactions.poly.on('drawend', endCallback);

interactions.ls.on('drawstart', function(evt) {
  var tooltipCoord = evt.coordinate;
  listener = evt.feature.getGeometry().on('change', function(evt) {
    tooltipCoord = evt.target.getLastCoordinate();
    var length = measure.getLength(evt.target, map.getView().getProjection());
    overlay.getElement().innerHTML = (Math.round(length * 0.1) / 100) + ' ' + 'km';
    overlay.setPosition(tooltipCoord);
    overlay.getElement().style.display = 'block';
  });
});
interactions.poly.on('drawstart', function(evt) {
  var tooltipCoord = evt.coordinate;
  listener = evt.feature.getGeometry().on('change', function(evt) {
    tooltipCoord = evt.target.getInteriorPoint().getCoordinates();
    var area = measure.getArea(evt.target, map.getView().getProjection());
    overlay.getElement().innerHTML = (Math.round(area / 1000000 * 100) / 100) +
        ' ' + 'km2';
    overlay.setPosition(tooltipCoord);
    overlay.getElement().style.display = 'block';
  });
});

interactions.modify.on('modifyend', function(evt) {
  // move modified feature to draw layer if not already there
  // FIXME: this is not very satisfactory, as is triggered on every vertex move/delete/add
  var f = evt.features.item(0);
  var layer = select.get().getLayer(f);
  if (layer.get('id') !== DRAWID) {
    layer.getSource().removeFeature(f);
    source.addFeatures([f]);
  }
});

var i;
// interactions inactive until clicked
for (i in interactions) {
  map.addInteraction(interactions[i]);
  interactions[i].setActive(false);
}

var wasDelete = false, wasModAtts = false, evtKey;

var op = component.getTemplate('serialise');
$('#toolbar').appendChild(op);

// add toggle listener to toolbartitle div
$('#serialise-title').addEventListener('click', function() {
  // toggle display of next, i.e. content, element
  var el = this.nextElementSibling;
  el.style.display = (el.style.display == 'block') ? 'none' : 'block';
});

var formatType = 'GeoJSON'; // default output format
var saveall;
$('#reserialise').onclick = function(e) {
  serialise(saveall, $('#tolerance').value);
};

$('#download').onclick = function(e) {
  var mediaTypes = {
    GeoJSON: 'application/vnd.geo+json',
    GPX: 'application/gpx+xml',
    KML: 'application/vnd.google-earth.kml+xml',
    mongo: 'application/json'
  };
  e.target.href = 'data:' + mediaTypes[formatType]
      + ';charset=utf-8,' + encodeURIComponent($('#serialOP').value);
  var ext = (formatType == 'mongo') ? 'json' : formatType.toLowerCase();
  e.target.download = 'myGeoData.' + ext;
};

function serialise(saveType, tolerance) {
  formatType = $('#opformat').value;
  var format = vectors.getFormat(formatType);
  // draw layer
  var outFeatures = source.getFeatures();
  if (saveType == 'saveall') {
    // add features from other vector sources
    var layers = vectors.getLayers().getArray();
    layers.forEach(function(l) {
      if (l.get('id') != DRAWID) {
        outFeatures = outFeatures.concat(l.getSource().getFeatures());
      }
    });
  }
  // geometry only output only makes sense if GeoJSON and only 1 feature
  // FIXME well, all right, it could output a GeometryCollection
  var geoonly = $('#geoonly').checked && formatType == 'GeoJSON' &&
      outFeatures.length === 1;
  function doWrite(features, reproject, geoonly) {
    var opts = {
      decimals: DECIMALS
    };
    if (reproject) {
      opts.featureProjection = map.getView().getProjection();
    }
    var out = geoonly ?
      format.writeGeometry(features[0].getGeometry(), opts) :
      format.writeFeatures(features, opts);
    return out;
  }
  var op = doWrite(outFeatures, true, geoonly);
  if (tolerance) {
    // bit long-winded to read features and write out again, but has advantages:
    // - simplify does not operate on the rendered features
    // - features are always in 4326 latlons
    // browser seems to struggle with large datasets though
    if (geoonly) {
      var feats = [new ol.Feature(format.readGeometry(op).simplify(tolerance))];
      op = doWrite(feats, false, geoonly);
    } else {
      var serFeatures = [];
      serFeatures = format.readFeatures(op);
      serFeatures.forEach(function(f) {
        f.setGeometry(f.getGeometry().simplify(tolerance));
      });
      op = doWrite(serFeatures, false, geoonly);
    }
  }
  $('#serialOP').value = op;
}

$('#drawtype').onchange = changeFunction;

function changeFunction(e) {
  // clear any selected features
  features.clear();
  // set any previously active interactions inactive
  for (var i in interactions) {
    interactions[i].setActive(false);
  }
  // ... and clicked one active
  if (interactions[e.target.value]) {
    interactions[e.target.value].setActive(true);
  }

  // if delete, add listener to delete from source and select when selected
  if (e.target.value == 'deleet') {
    evtKey = features.on('add', function(e) {
      select.get().getLayer(e.element).getSource().removeFeature(e.element);
      features.clear();
    });
    wasDelete = true;
  } else if (wasDelete) {
    ol.Observable.unByKey(evtKey);
    wasDelete = false;
  }

  if (e.target.value == 'hide' || e.target.value == 'choose') {
    select.drawOff();
    if (e.target.value == 'hide') {
      this.style.display = 'none';
      $('#drawoption').style.display = 'block';
      $('#drawoption').value = 'redraw';
      $('#drawtype').value = 'choose';
    }
  } else {
    select.drawOn();
  }

  if (['point', 'ls', 'poly'].indexOf(e.target.value) == -1) {
    select.get().setActive(true);
  } else {
    // no select while drawing
    select.get().setActive(false);
  }

  if (e.target.value == 'modAtts') {
    evtKey = features.on('add', function(e) {
      var el = overlay.getElement();
      // may be featuredisplay present
      while (el.firstChild) {
        el.removeChild(el.firstChild);
      }
      el.appendChild(form.cloneNode(true));

      var feature = e.element;
      $('#featureformid').value = feature.getId();
      // display existing atts
      var atts = feature.getProperties();
      var attsDiv = $('#featureformAtts');
      for (var att in atts) {
        if (att != 'geometry') {
          var clone = attsDiv.querySelector('template').content
              .firstElementChild.cloneNode(true);
          var inputs = clone.querySelectorAll('input');
          inputs[0].value = att;
          inputs[1].value = atts[att];
          attsDiv.insertBefore(clone, attsDiv.firstElementChild);
        }
      }
      // use onclick rather than addEventListener so only 1 active
      $('#plusbutton').onclick = plusFunc;
      $('#featurebutton').onclick = function() {
        feature.setId($('#featureformid').value);
        var divs = $('#featureformAtts').querySelectorAll('div');
        for (var i = 0; i < divs.length; i++) {
          var inputs = divs[i].querySelectorAll('input');
          if (inputs[1].value) {
            feature.set(inputs[0].value, inputs[1].value);
          } else {
            // delete attribute if value cleared
            feature.unset(inputs[0].value);
          }
        }
        el.style.display = 'none';
        // move feature to draw layer if not already there
        var layer = select.get().getLayer(feature);
        if (layer.get('id') !== DRAWID) {
          layer.getSource().removeFeature(feature);
          source.addFeatures([feature]);
        }
      };
      overlay.setPosition(map.getView().getCenter());
      el.style.display = 'block';
    });
    wasModAtts = true;
  } else if (wasModAtts) {
    ol.Observable.unByKey(evtKey);
    wasModAtts = false;
  }

  if (e.target.value == 'save' || e.target.value == 'saveall') {
    saveall = e.target.value;
    serialise(saveall);
  }

  if (e.target.value == 'split') {
    var origCoords;
    select.get().once('select', function(evt) {
      // convert original coords to an array of strings
      // this bit can be removed once modify event returns modified vertex
      if (evt.selected[0]) {
        origCoords = evt.selected[0].getGeometry().getCoordinates().join(' ').split(' ');
      }
    });
    interactions.split.once('modifyend', function(evt) {
      var f = evt.features.item(0);
      var modCoords = f.getGeometry().getCoordinates();
      var i = 0;
      // convert each new coord to string and compare with original until find match
      while (i < modCoords.length && origCoords.indexOf(modCoords[i].join(',')) !== -1) {
        i++;
      }
      var part1 = modCoords.slice(0, i + 1); // include the new coord in both parts
      var part2 = modCoords.slice(i);
      var atts = f.getProperties();
      atts.geometry = new ol.geom.LineString(part1);
      var f1 = new ol.Feature(atts);
      atts.geometry = new ol.geom.LineString(part2);
      var f2 = new ol.Feature(atts);
      // effectively, this moves a split feature on layer other than draw layer to the draw layer
      source.addFeatures([f1, f2]);
      select.get().getLayer(f).getSource().removeFeature(f);
      features.clear();
      e.target.value = 'choose';
      changeFunction(e);
    });
  }

  if (e.target.value == 'join') {
    joinKey = select.get().on('select', function(evt) {
      if (features.getLength() == 2) {
        var f1 = features.item(0);
        var f2 = features.item(1);
        var atts = f1.getProperties();
        atts.geometry = new ol.geom.LineString(
          f1.getGeometry().getCoordinates().concat(f2.getGeometry().getCoordinates().slice(1))
        );
        var f3 = new ol.Feature(atts);
        features.clear();
        // effectively, this moves the joined feature on layer other than draw layer to the draw layer
        select.get().getLayer(f2).getSource().removeFeature(f2);
        select.get().getLayer(f1).getSource().removeFeature(f1);
        source.addFeatures([f3]);
      }
    });
  } else if (joinKey) {
    ol.Observable.unByKey(joinKey);
    joinKey = null;
  }

  if (e.target.value == 'clear') {
    source.clear();
  }

  this.blur();
}

// add helptemplate to toolbar
$('#help-content').appendChild(component.getTemplate('drawhelp'));
// add toggle listener to helptitle div
$('#draw-title').addEventListener('click', function() {
  // toggle display of next, i.e. content, element
  var el = this.nextElementSibling;
  el.style.display = (el.style.display == 'block') ? 'none' : 'block';
});
<commit_msg>Small improvement in line split<commit_after>/**
 * Component for drawing and editing of vector data.
 * Uses popup, measure.
 * Screen position of buttons determined by css in html file.
 */

// import templates
var tpl = require('components/draw.html');
var Component = require('components/component');
var component = new Component(tpl, 'draw');

var ol = require('ol');
var map = require('olMap').get();
var utils = require('utils');
var $ = utils.$;
// make sure toolbar initialised
require('components/toolbar');

// add drawtemplate to map
// use ol.control to prevent event propagation
map.addControl(new ol.control.Control({element: component.getTemplate('draw')}));

var select = require('select');
var vectors = require('vectors');
var measure = require('measure');
var overlay = require('components/popup').getOverlay();

var joinKey;
// FIXME English
var DRAWID = 'Drawn';
// FIXME should be configurable
var DECIMALS = 6; // coordinate decimal places, used by save

// add a vector layer/source
// FIXME enable drawing on existing layers
vectors.add({vectors: [{id: DRAWID}]});
var source = vectors.getLayers().item(vectors.getLayers().getLength() - 1)
    .getSource();

// create and add draw/modify interactions
var features = select.get().getFeatures();
var interactions = {
  point: new ol.interaction.Draw({
    source: source,
    type: 'Point'
  }),
  ls: new ol.interaction.Draw({
    source: source,
    type: 'LineString'
  }),
  poly: new ol.interaction.Draw({
    source: source,
    type: 'Polygon'
  }),
  modify: new ol.interaction.Modify({
    features: features
  }),
  split: new ol.interaction.Modify({
    features: features,
    deleteCondition: ol.events.condition.never
  })
};

var listener;
var form = component.getTemplate('featureform');

function plusFunc() {
  var temp = this.parentNode.querySelector('template');
  var clone = temp.content.firstElementChild.cloneNode(true);
  $('#featureformAtts').insertBefore(clone, temp);
  var i = $('#featureformAtts').querySelectorAll('input');
  i[i.length - 2].focus(); // focus on 1st new input field
}

var endCallback = function(e) {
  var el = overlay.getElement();
  // may be featuredisplay present
  while (el.firstChild) {
    el.removeChild(el.firstChild);
  }
  el.appendChild(form.cloneNode(true));
  // initialise displayed atts
  $('#featureformid').value = '';
  $('#plusbutton').onclick = plusFunc;
  // use onclick rather than addEventListener so only 1 active
  $('#featurebutton').onclick = function() {
    if ($('#featureformid').value) {
      e.feature.setId($('#featureformid').value);
    }
    var divs = $('#featureformAtts').querySelectorAll('div');
    for (var i = 0; i < divs.length; i++) {
      var inputs = divs[i].querySelectorAll('input');
      // only set attribute if there is a key
      if (inputs[0].value) {
        e.feature.set(inputs[0].value, inputs[1].value);
      }
    }
    el.style.display = 'none';
  };
  overlay.setPosition(map.getView().getCenter());
  el.style.display = 'block';
  $('#featureformid').focus();
  if (listener) {
    ol.Observable.unByKey(listener);
  }
};

interactions.point.on('drawend', endCallback);
interactions.ls.on('drawend', endCallback);
interactions.poly.on('drawend', endCallback);

interactions.ls.on('drawstart', function(evt) {
  var tooltipCoord = evt.coordinate;
  listener = evt.feature.getGeometry().on('change', function(evt) {
    tooltipCoord = evt.target.getLastCoordinate();
    var length = measure.getLength(evt.target, map.getView().getProjection());
    overlay.getElement().innerHTML = (Math.round(length * 0.1) / 100) + ' ' + 'km';
    overlay.setPosition(tooltipCoord);
    overlay.getElement().style.display = 'block';
  });
});
interactions.poly.on('drawstart', function(evt) {
  var tooltipCoord = evt.coordinate;
  listener = evt.feature.getGeometry().on('change', function(evt) {
    tooltipCoord = evt.target.getInteriorPoint().getCoordinates();
    var area = measure.getArea(evt.target, map.getView().getProjection());
    overlay.getElement().innerHTML = (Math.round(area / 1000000 * 100) / 100) +
        ' ' + 'km2';
    overlay.setPosition(tooltipCoord);
    overlay.getElement().style.display = 'block';
  });
});

interactions.modify.on('modifyend', function(evt) {
  // move modified feature to draw layer if not already there
  // FIXME: this is not very satisfactory, as is triggered on every vertex move/delete/add
  var f = evt.features.item(0);
  var layer = select.get().getLayer(f);
  if (layer.get('id') !== DRAWID) {
    layer.getSource().removeFeature(f);
    source.addFeatures([f]);
  }
});

var i;
// interactions inactive until clicked
for (i in interactions) {
  map.addInteraction(interactions[i]);
  interactions[i].setActive(false);
}

var wasDelete = false, wasModAtts = false, evtKey;

var op = component.getTemplate('serialise');
$('#toolbar').appendChild(op);

// add toggle listener to toolbartitle div
$('#serialise-title').addEventListener('click', function() {
  // toggle display of next, i.e. content, element
  var el = this.nextElementSibling;
  el.style.display = (el.style.display == 'block') ? 'none' : 'block';
});

var formatType = 'GeoJSON'; // default output format
var saveall;
$('#reserialise').onclick = function(e) {
  serialise(saveall, $('#tolerance').value);
};

$('#download').onclick = function(e) {
  var mediaTypes = {
    GeoJSON: 'application/vnd.geo+json',
    GPX: 'application/gpx+xml',
    KML: 'application/vnd.google-earth.kml+xml',
    mongo: 'application/json'
  };
  e.target.href = 'data:' + mediaTypes[formatType]
      + ';charset=utf-8,' + encodeURIComponent($('#serialOP').value);
  var ext = (formatType == 'mongo') ? 'json' : formatType.toLowerCase();
  e.target.download = 'myGeoData.' + ext;
};

function serialise(saveType, tolerance) {
  formatType = $('#opformat').value;
  var format = vectors.getFormat(formatType);
  // draw layer
  var outFeatures = source.getFeatures();
  if (saveType == 'saveall') {
    // add features from other vector sources
    var layers = vectors.getLayers().getArray();
    layers.forEach(function(l) {
      if (l.get('id') != DRAWID) {
        outFeatures = outFeatures.concat(l.getSource().getFeatures());
      }
    });
  }
  // geometry only output only makes sense if GeoJSON and only 1 feature
  // FIXME well, all right, it could output a GeometryCollection
  var geoonly = $('#geoonly').checked && formatType == 'GeoJSON' &&
      outFeatures.length === 1;
  function doWrite(features, reproject, geoonly) {
    var opts = {
      decimals: DECIMALS
    };
    if (reproject) {
      opts.featureProjection = map.getView().getProjection();
    }
    var out = geoonly ?
      format.writeGeometry(features[0].getGeometry(), opts) :
      format.writeFeatures(features, opts);
    return out;
  }
  var op = doWrite(outFeatures, true, geoonly);
  if (tolerance) {
    // bit long-winded to read features and write out again, but has advantages:
    // - simplify does not operate on the rendered features
    // - features are always in 4326 latlons
    // browser seems to struggle with large datasets though
    if (geoonly) {
      var feats = [new ol.Feature(format.readGeometry(op).simplify(tolerance))];
      op = doWrite(feats, false, geoonly);
    } else {
      var serFeatures = [];
      serFeatures = format.readFeatures(op);
      serFeatures.forEach(function(f) {
        f.setGeometry(f.getGeometry().simplify(tolerance));
      });
      op = doWrite(serFeatures, false, geoonly);
    }
  }
  $('#serialOP').value = op;
}

$('#drawtype').onchange = changeFunction;

function changeFunction(e) {
  // clear any selected features
  features.clear();
  // set any previously active interactions inactive
  for (var i in interactions) {
    interactions[i].setActive(false);
  }
  // ... and clicked one active
  if (interactions[e.target.value]) {
    interactions[e.target.value].setActive(true);
  }

  // if delete, add listener to delete from source and select when selected
  if (e.target.value == 'deleet') {
    evtKey = features.on('add', function(e) {
      select.get().getLayer(e.element).getSource().removeFeature(e.element);
      features.clear();
    });
    wasDelete = true;
  } else if (wasDelete) {
    ol.Observable.unByKey(evtKey);
    wasDelete = false;
  }

  if (e.target.value == 'hide' || e.target.value == 'choose') {
    select.drawOff();
    if (e.target.value == 'hide') {
      this.style.display = 'none';
      $('#drawoption').style.display = 'block';
      $('#drawoption').value = 'redraw';
      $('#drawtype').value = 'choose';
    }
  } else {
    select.drawOn();
  }

  if (['point', 'ls', 'poly'].indexOf(e.target.value) == -1) {
    select.get().setActive(true);
  } else {
    // no select while drawing
    select.get().setActive(false);
  }

  if (e.target.value == 'modAtts') {
    evtKey = features.on('add', function(e) {
      var el = overlay.getElement();
      // may be featuredisplay present
      while (el.firstChild) {
        el.removeChild(el.firstChild);
      }
      el.appendChild(form.cloneNode(true));

      var feature = e.element;
      $('#featureformid').value = feature.getId();
      // display existing atts
      var atts = feature.getProperties();
      var attsDiv = $('#featureformAtts');
      for (var att in atts) {
        if (att != 'geometry') {
          var clone = attsDiv.querySelector('template').content
              .firstElementChild.cloneNode(true);
          var inputs = clone.querySelectorAll('input');
          inputs[0].value = att;
          inputs[1].value = atts[att];
          attsDiv.insertBefore(clone, attsDiv.firstElementChild);
        }
      }
      // use onclick rather than addEventListener so only 1 active
      $('#plusbutton').onclick = plusFunc;
      $('#featurebutton').onclick = function() {
        feature.setId($('#featureformid').value);
        var divs = $('#featureformAtts').querySelectorAll('div');
        for (var i = 0; i < divs.length; i++) {
          var inputs = divs[i].querySelectorAll('input');
          if (inputs[1].value) {
            feature.set(inputs[0].value, inputs[1].value);
          } else {
            // delete attribute if value cleared
            feature.unset(inputs[0].value);
          }
        }
        el.style.display = 'none';
        // move feature to draw layer if not already there
        var layer = select.get().getLayer(feature);
        if (layer.get('id') !== DRAWID) {
          layer.getSource().removeFeature(feature);
          source.addFeatures([feature]);
        }
      };
      overlay.setPosition(map.getView().getCenter());
      el.style.display = 'block';
    });
    wasModAtts = true;
  } else if (wasModAtts) {
    ol.Observable.unByKey(evtKey);
    wasModAtts = false;
  }

  if (e.target.value == 'save' || e.target.value == 'saveall') {
    saveall = e.target.value;
    serialise(saveall);
  }

  if (e.target.value == 'split') {
    var origCoords;
    select.get().once('select', function(evt) {
      // this bit can be removed once modify event returns modified vertex
      if (evt.selected[0]) {
        origCoords = evt.selected[0].getGeometry().getCoordinates();
      }
    });
    interactions.split.once('modifyend', function(evt) {
      var f = evt.features.item(0);
      var modCoords = f.getGeometry().getCoordinates();
      // return true/false whether val is in arr
      function check(arr, val) {
        return arr.some(function(arrval) {
          // have to convert to strings as arrays won't be equal
          return val.toString() == arrval.toString();
        });
      }
      var i = 0;
      // compare each new coord with original until find non-match
      while (i < modCoords.length && check(origCoords, modCoords[i]) == true) {
        i++;
      }
      var part1 = modCoords.slice(0, i + 1); // include the new coord in both parts
      var part2 = modCoords.slice(i);
      var atts = f.getProperties();
      atts.geometry = new ol.geom.LineString(part1);
      var f1 = new ol.Feature(atts);
      atts.geometry = new ol.geom.LineString(part2);
      var f2 = new ol.Feature(atts);
      // effectively, this moves a split feature on layer other than draw layer to the draw layer
      source.addFeatures([f1, f2]);
      select.get().getLayer(f).getSource().removeFeature(f);
      features.clear();
      e.target.value = 'choose';
      changeFunction(e);
    });
  }

  if (e.target.value == 'join') {
    joinKey = select.get().on('select', function(evt) {
      if (features.getLength() == 2) {
        var f1 = features.item(0);
        var f2 = features.item(1);
        var atts = f1.getProperties();
        atts.geometry = new ol.geom.LineString(
          f1.getGeometry().getCoordinates().concat(f2.getGeometry().getCoordinates().slice(1))
        );
        var f3 = new ol.Feature(atts);
        features.clear();
        // effectively, this moves the joined feature on layer other than draw layer to the draw layer
        select.get().getLayer(f2).getSource().removeFeature(f2);
        select.get().getLayer(f1).getSource().removeFeature(f1);
        source.addFeatures([f3]);
      }
    });
  } else if (joinKey) {
    ol.Observable.unByKey(joinKey);
    joinKey = null;
  }

  if (e.target.value == 'clear') {
    source.clear();
  }

  this.blur();
}

// add helptemplate to toolbar
$('#help-content').appendChild(component.getTemplate('drawhelp'));
// add toggle listener to helptitle div
$('#draw-title').addEventListener('click', function() {
  // toggle display of next, i.e. content, element
  var el = this.nextElementSibling;
  el.style.display = (el.style.display == 'block') ? 'none' : 'block';
});
<|endoftext|>


<commit_before>import React from 'react';
import { Provider } from 'react-redux';

let devTools = null;

if(process.env.NODE_ENV === 'development') {
  const DevTools = require('./components/DevTools.js').default;
  devTools = <DevTools/>;
}

class Root extends React.Component {
  render() {
    return (
      <Provider store={this.props.store}>

          {this.props.children}
          {devTools}

      </Provider>
    );
  }
}

export default Root;
<commit_msg>refactor: - remove render devtools on server<commit_after>import React from 'react';
import { Provider } from 'react-redux';

let devTools = null;

if(process.env.NODE_ENV === 'development') {
  const DevTools = require('./components/DevTools.js').default;
  devTools = <DevTools/>;
}

class Root extends React.Component {
  constructor(props, context) {
    super(props);
    this.state = {};
  }

  componentDidMount() {
    if (devTools) {
      this.setState({ devTools });
    }
  }

  render() {
    return (
      <Provider store={this.props.store}>

          {this.props.children}
          {this.state.devTools}

      </Provider>
    );
  }
}

export default Root;
<|endoftext|>


<commit_before>module.exports = class PlaylistController {
  constructor(playlist) {
    this.playlist = playlist;
  }

  async add(ctx) {
    const unavailable_links = await this.playlist.add(ctx.request.body);

    if (unavailable_links.length !== 0) {
      ctx.body = unavailable_links;
    } else {
      ctx.body = [];
    }

    ctx.status = 200;
  }

  async clear(ctx) {
    this.playlist.replace();
    ctx.status = 200;
  }

  async remove(ctx, index) {
    this.playlist.remove(index);
    ctx.status = 200;
  }
};
<commit_msg>refactor add in playlist_controller<commit_after>module.exports = class PlaylistController {
  constructor(playlist) {
    this.playlist = playlist;
  }

  async add(ctx) {
    ctx.body = await this.playlist.add(ctx.request.body); // unavailable_links
    ctx.status = 200;
  }

  async clear(ctx) {
    this.playlist.replace();
    ctx.status = 200;
  }

  async remove(ctx, index) {
    this.playlist.remove(index);
    ctx.status = 200;
  }
};
<|endoftext|>


<commit_before>//{{{
/***
|''Requires''|TiddlyWebConfig chrjs|
!HTMLForm
<form action="#">
	<fieldset>
		<legend />

Username:

<input type="text" name="identity" />

		<input type="submit" />
	</fieldset>
</form>
!Code
***/
//{{{
(function($) {

var macro = config.macros.TiddlySpaceIdentities = {
	formTemplate: store.getTiddlerText(tiddler.title + "##HTMLForm"),
	locale: {
		label: "Add Identiy",
		success: "successfully added identity %0",
		error: "error adding identity %0: %1"
	},

	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		config.extensions.TiddlyWeb.getUserInfo(function(user) {
			if(!user.anon) {
				$(macro.formTemplate).
					find("legend").text(macro.locale.label).end().
					find("input[type=submit]").val(macro.locale.label).
						click(macro.onSubmit).end().
					appendTo(place);
			}
		});
	},
	onSubmit: function(ev) {
		var form = $(this).closest("form");
		var identity = form.find("input[name=identity]").val();
		var tiddler = new tiddlyweb.Tiddler(identity);
		tiddler.bag = new tiddlyweb.Bag("MAPUSER", config.extensions.TiddlyWeb.host);
		var callback = function(data, status, xhr) {
			displayMessage(macro.locale.success.format([identity]));
		};
		var errback = function(xhr, error, exc) {
			displayMessage(macro.locale.error.format([identity, error]));
		};
		tiddler.put(callback, errback);
		return false;
	}
};

})(jQuery);
//}}}
<commit_msg>TiddlySpaceIdentities: validate OpenID before adding it as identity<commit_after>//{{{
/***
|''Requires''|TiddlyWebConfig chrjs|
!HTMLForm
<form action="#">
	<fieldset>
		<legend />

Type:

				<select>
					<option>OpenID</option>
				</select>

Username:

<input type="text" name="identity" />

		<input type="submit" />
	</fieldset>
</form>
!Code
***/
//{{{
(function($) {

var host = config.extensions.TiddlyWeb.host;

var macro = config.macros.TiddlySpaceIdentities = {
	formTemplate: store.getTiddlerText(tiddler.title + "##HTMLForm"),
	locale: {
		label: "Add Identity",
		success: "successfully added identity %0",
		error: "error adding identity %0: %1",
		authError: "error authenticating identity %0: %1"
	},

	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		config.extensions.TiddlyWeb.getUserInfo(function(user) {
			if(!user.anon) {
				$(macro.formTemplate).
					find("legend").text(macro.locale.label).end().
					find("input[type=submit]").val(macro.locale.label).
						click(macro.onSubmit).end().
					appendTo(place);
			}
		});
	},
	onSubmit: function(ev) {
		var form = $(this).closest("form");
		var identity = form.find("input[name=identity]").val();
		macro.authenticate(identity, function(data, status, xhr) {
			window.location = xhr.getResponseHeader("Location");
		});
		return false;
	},
	authenticate: function(identity, callback) {
		var challenger = "tiddlywebplugins.tiddlyspace.openid"; // XXX: hardcoded
		var uri = host + "/challenge/" + challenger;
		$.ajax({
			url: uri,
			type: "POST",
			data: { // XXX: hardcoded
				openid: identity,
				tiddlyweb_redirect: host + "%0/#auth:OpenID"
			},
			success: callback,
			error: function(xhr, error, exc) {
				displayMessage(macro.locale.authError.format([identity, error]));
			}
		});
	},
	addIdentity: function(name) {
		var tiddler = new tiddlyweb.Tiddler(name);
		tiddler.bag = new tiddlyweb.Bag("MAPUSER", host);
		var callback = function(data, status, xhr) {
			displayMessage(macro.locale.success.format([identity]));
		};
		var errback = function(xhr, error, exc) {
			displayMessage(macro.locale.error.format([identity, error]));
		};
		tiddler.put(callback, errback);
	}
};

})(jQuery);
//}}}
<|endoftext|>


<commit_before>(function (){
    "use strict";
    function createMenuItem(parent, label, link, title){
        var li = document.createElement("li");
        li.className = "yui3-menuitem";
        parent.appendChild(li);
        var a = document.createElement("a");
        li.appendChild(a);
        a.className = "yui3-menuitem-content";
        a.href = link;
        a.appendChild(document.createTextNode(label));
        if (title){
            a.title = title;
        }
    }

    function buildSubMenu(parent, label, id){
        var li = document.createElement("li");
        parent.appendChild(li);
        var a = document.createElement("a");
        li.appendChild(a);
        a.className = "yui3-menu-label";
        a.href = "#"+id;
        a.appendChild(document.createTextNode(label));
        var subMenu = document.createElement("div");
        subMenu.className = "yui3-menu";
        subMenu.id = id;
        li.appendChild(subMenu);
        var subMenuContent = document.createElement("div");
        subMenuContent.className = "yui3-menu-content";
        subMenu.appendChild(subMenuContent);
        var ul = document.createElement("uv");
        subMenuContent.appendChild(ul);
        return ul;
    }

    function buildTopMenu(){
        var header = document.getElementsByTagName("header")[0];
        header.className = "yui3-g";
        var divU = document.createElement("div");
        divU.className = "yui3-u";
        divU.style.width = "100%";

        header.appendChild(divU);
        var topmenu = document.createElement("div");
        divU.appendChild(topmenu);
        topmenu.className = "yui3-menu yui3-menu-horizontal";
        topmenu.id = "topmenu";

        var menuContent = document.createElement("div");
        menuContent.className = "yui3-menu-content";
        topmenu.appendChild(menuContent);
        var ul = document.createElement("ul");
        menuContent.appendChild(ul);

        createMenuItem(ul, "KickJS", "http://www.kickjs.org/");
        var documentationSubMenu = buildSubMenu(ul,"Documentation","documentation");
        createMenuItem(documentationSubMenu, "API", "/api/index.html");
        createMenuItem(documentationSubMenu, "Tutorial", "/tutorial/index.html");
        createMenuItem(documentationSubMenu, "Tutorial - 0 - Installation", "/tutorial/part0.html");
        createMenuItem(documentationSubMenu, "Tutorial - 1 - The component based scenegraph", "/tutorial/part1.html");
        createMenuItem(documentationSubMenu, "Tutorial - 2 - Materials and shaders", "/tutorial/part2.html");
        createMenuItem(documentationSubMenu, "Tutorial - 3 - Shader Editor", "/tutorial/part3.html");
        createMenuItem(documentationSubMenu, "Tutorial - 4 - Scripting and time", "/tutorial/part4.html");
        createMenuItem(documentationSubMenu, "Tutorial - 5 - Key Input", "/tutorial/part5.html");
        var examplesSubMenu = buildSubMenu(ul,"Examples","examples");
        createMenuItem(examplesSubMenu, "Shader editor", "/example/shader_editor/shader_editor.html");
        createMenuItem(examplesSubMenu, "Video ascii art", "/example/video_ascii_art/Video_Ascii_Art.html");
        createMenuItem(examplesSubMenu, "Model viewer", "/example/model_viewer/model_viewer.html");
        createMenuItem(examplesSubMenu, "Cloth simulation", "/example/cloth_simulation/cloth_simulation.html");
        createMenuItem(examplesSubMenu, "Snake", "/example/snake/snake.html");
        createMenuItem(examplesSubMenu, "Chess", "/example/chess/chess.html");
        createMenuItem(ul, "Source", "https://github.com/mortennobel/KickJS", "Find the full source on GitHub");
        createMenuItem(ul, "Bugs", "https://github.com/mortennobel/KickJS/issues", "Report bugs on GitHub");
        createMenuItem(ul, "Ask a question", "http://stackoverflow.com/questions/ask?tags=kickjs,webgl", "Questions? We will be happy to help you on StackOverflow.com.");
    }

    if (!window.YUI){
        console.log("YUI not loaded");
        return;
    }

    window.YUI().use('node-menunav', function (Y) {
        buildTopMenu();

        var menu = Y.one("#topmenu");

        menu.plug(Y.Plugin.NodeMenuNav);
    });
})();<commit_msg>Improve header<commit_after>(function (){
    "use strict";
    function createMenuItem(parent, label, link, title){
        var li = document.createElement("li");
        li.className = "yui3-menuitem";
        parent.appendChild(li);
        var a = document.createElement("a");
        li.appendChild(a);
        a.className = "yui3-menuitem-content";
        a.href = link;
        a.appendChild(document.createTextNode(label));
        if (title){
            a.title = title;
        }
    }

    function buildSubMenu(parent, label, id){
        var li = document.createElement("li");
        parent.appendChild(li);
        var a = document.createElement("a");
        li.appendChild(a);
        a.className = "yui3-menu-label";
        a.href = "#"+id;
        a.appendChild(document.createTextNode(label));
        var subMenu = document.createElement("div");
        subMenu.className = "yui3-menu";
        subMenu.id = id;
        li.appendChild(subMenu);
        var subMenuContent = document.createElement("div");
        subMenuContent.className = "yui3-menu-content";
        subMenu.appendChild(subMenuContent);
        var ul = document.createElement("uv");
        subMenuContent.appendChild(ul);
        return ul;
    }

    function buildTopMenu(){
        var header = document.getElementsByTagName("header")[0];
        header.className = "yui3-g";
        var divU = document.createElement("div");
        divU.className = "yui3-u";
        divU.style.width = "100%";

        header.appendChild(divU);
        var topmenu = document.createElement("div");
        divU.appendChild(topmenu);
        topmenu.className = "yui3-menu yui3-menu-horizontal";
        topmenu.id = "topmenu";

        var menuContent = document.createElement("div");
        menuContent.className = "yui3-menu-content";
        topmenu.appendChild(menuContent);
        var ul = document.createElement("ul");
        menuContent.appendChild(ul);

        createMenuItem(ul, "KickJS", "http://www.kickjs.org/");
        var documentationSubMenu = buildSubMenu(ul,"Documentation","documentation");
        createMenuItem(documentationSubMenu, "API", "/api/index.html");
        createMenuItem(documentationSubMenu, "Tutorial", "/tutorial/index.html");
        createMenuItem(documentationSubMenu, "Tutorial - 0 - Installation", "/tutorial/part0.html");
        createMenuItem(documentationSubMenu, "Tutorial - 1 - The component based scenegraph", "/tutorial/part1.html");
        createMenuItem(documentationSubMenu, "Tutorial - 2 - Materials and shaders", "/tutorial/part2.html");
        createMenuItem(documentationSubMenu, "Tutorial - 3 - Shader Editor", "/tutorial/part3.html");
        createMenuItem(documentationSubMenu, "Tutorial - 4 - Scripting and time", "/tutorial/part4.html");
        createMenuItem(documentationSubMenu, "Tutorial - 5 - Key Input", "/tutorial/part5.html");
        var examplesSubMenu = buildSubMenu(ul,"Examples","examples");
        createMenuItem(examplesSubMenu, "Shader editor", "/example/shader_editor/shader_editor.html");
        createMenuItem(examplesSubMenu, "Video ascii art", "/example/video_ascii_art/Video_Ascii_Art.html");
        createMenuItem(examplesSubMenu, "Model viewer", "/example/model_viewer/model_viewer.html");
        createMenuItem(examplesSubMenu, "Cloth simulation", "/example/cloth_simulation/cloth_simulation.html");
        createMenuItem(examplesSubMenu, "Snake", "/example/snake/snake.html");
        createMenuItem(examplesSubMenu, "Chess", "/example/chess/chess.html");
        createMenuItem(ul, "Source", "https://github.com/mortennobel/KickJS", "Find the full source on GitHub");
        createMenuItem(ul, "Bugs", "https://github.com/mortennobel/KickJS/issues", "Report bugs on GitHub");
        createMenuItem(ul, "Ask a question", "http://stackoverflow.com/questions/ask?tags=webgl&title=KickJS", "Questions? We will be happy to help you on StackOverflow.com.");
    }

    if (!window.YUI){
        console.log("YUI not loaded");
        return;
    }

    window.YUI().use('node-menunav', function (Y) {
        buildTopMenu();

        var menu = Y.one("#topmenu");

        menu.plug(Y.Plugin.NodeMenuNav);
    });
})();<|endoftext|>


<commit_before>'use strict'

const Slack = require('slack-client')
const util = require('util')
const EventEmitter = require('events')
const winston = require('winston')

const COMMANDS = new Set(["apps", "events", "status"])

class SlackMessageController extends EventEmitter {
  constructor (credentials) {
    super()

    this.slack = new Slack(credentials, true, true)
    this.slack.on('message', this.process_message.bind(this))
    this.slack.on('loggedIn', this.update_bot_user.bind(this))
    this.slack.on('error', this.on_error.bind(this))
    this.slack.on('open', this.on_open.bind(this))

    this.slack.login()
  }

  update_bot_user(user) {
    this.bot_reply_regex = new RegExp('^<@' + user.id + '>')
  }

  send_to_all_channels(message) {
    this.channels.forEach( channel => this.send_to_channel(channel, message) )
  }

  send_to_channel(channel, message) {
    this.slack.getChannelGroupOrDMByID(channel).send(message)
  }

  process_message (message) {
    if (message.text && message.text.match(this.bot_reply_regex)) {
      var commands = message.text.split(' ')
      commands.splice(0, 1)

      if (!commands.length) {
        this.send_to_channel(message.channel, 'Hello, how can I help you?\nThe following commands are available: _apps_, _events_ and _status_.')
        return
      }

      var action = commands[0],
        parameters = commands.slice(1)

      if (!COMMANDS.has(action)) {
        this.send_to_channel(message.channel, 'Hmmm, I don\'t recognise that command...')
        return
      }

      this.emit(action, message.channel, parameters)
    }
  }

  on_error (error) {
    winston.error('Connection Error From Slack Websocket')
    winston.error(error)
  }

  on_open () {
    var channels = this.slack.channels
    this.channels = Object.keys(channels).filter( channel => channels[channel].is_member)
    var channel_names = this.channels.map( channel => channels[channel].name )

    this.emit('ready', channel_names)
  }
}

SlackMessageController.COMMANDS = COMMANDS

module.exports = SlackMessageController
<commit_msg>Fix bug with sending message to non-member channels<commit_after>'use strict'

const Slack = require('slack-client')
const EventEmitter = require('events')
const winston = require('winston')

const COMMANDS = new Set(['apps', 'events', 'status'])

class SlackMessageController extends EventEmitter {
  constructor (credentials) {
    super()

    this.slack = new Slack(credentials, true, true)
    this.slack.on('message', this.process_message.bind(this))
    this.slack.on('loggedIn', this.update_bot_user.bind(this))
    this.slack.on('error', this.on_error.bind(this))
    this.slack.on('open', this.on_open.bind(this))

    this.slack.login()
  }

  update_bot_user (user) {
    this.bot_reply_regex = new RegExp('^<@' + user.id + '>')
  }

  send_to_all_channels (message) {
    this.bot_channels().forEach(channel => this.send_to_channel(channel, message))
  }

  send_to_channel (channel, message) {
    this.slack.getChannelGroupOrDMByID(channel).send(message)
  }

  process_message (message) {
    if (message.text && message.text.match(this.bot_reply_regex)) {
      var commands = message.text.split(' ')
      commands.splice(0, 1)

      if (!commands.length) {
        this.send_to_channel(message.channel, 'Hello, how can I help you?\nThe following commands are available: _apps_, _events_ and _status_.')
        return
      }

      var action = commands[0],
        parameters = commands.slice(1)

      if (!COMMANDS.has(action)) {
        this.send_to_channel(message.channel, 'Hmmm, I don\'t recognise that command...')
        return
      }

      this.emit(action, message.channel, parameters)
    }
  }

  bot_channels () {
    var channel_keys = Object.keys(this.slack.channels)
    return channel_keys.filter(channel => this.slack.channels[channel].is_member)
  }

  on_error (error) {
    winston.error('Connection Error From Slack Websocket')
    winston.error(error)
  }

  on_open () {
    var bot_channels = this.bot_channels(),
      channel_names = bot_channels.map(channel => this.slack.channels[channel].name)
    this.emit('ready', channel_names)
  }
}

SlackMessageController.COMMANDS = COMMANDS

module.exports = SlackMessageController
<|endoftext|>