<commit_before>KityMinder.registerModule('image', function() {
    function loadImageSize(url, callback) {
        var img = document.createElement('img');
        img.onload = function() {
            callback(img.width, img.height);
        };
        img.onerror = function() {
            callback(null);
        };
        img.src = url;
    }

    function fitImageSize(width, height, maxWidth, maxHeight) {
        var ratio = width / height,
            fitRatio = maxWidth / maxHeight;

        // 宽高比大于最大尺寸的宽高比，以宽度为标准适应
        if (ratio > fitRatio && width > maxWidth) {
            width = maxWidth;
            height = maxWidth / ratio;
        } else if (height > maxHeight) {
            height = maxHeight;
            width = maxHeight / ratio;
        }

        return {
            width: width | 0,
            height: height | 0
        };
    }

    var ImageCommand = kity.createClass('ImageCommand', {
        base: Command,

        execute: function(km, url) {
            var nodes = km.getSelectedNodes();

            loadImageSize(url, function(width, height) {
                if (!width) return;
                utils.each(nodes, function(i, n) {
                    var size = fitImageSize(
                        width, height,
                        km.getOptions('maxImageWidth'),
                        km.getOptions('maxImageHeight'));
                    n.setData('image', url);
                    n.setData('imageSize', size);
                    n.render();
                });
                km.layout(300);
            });

        },
        queryState: function(km) {
            var nodes = km.getSelectedNodes(),
                result = 0;
            if (nodes.length === 0) {
                return -1;
            }
            utils.each(nodes, function(i, n) {
                if (n && n.getData('image')) {
                    result = 0;
                    return false;
                }
            });
            return result;
        },
        queryValue: function(km) {
            var node = km.getSelectedNode();
            return node.getData('image');
        }
    });

    var RemoveImageCommand = kity.createClass('RemoveImageCommand', {
        base: Command,

        execute: function(km) {
            var nodes = km.getSelectedNodes();
            utils.each(nodes, function(i, n) {
                n.setData('image').render();
            });
            km.layout(300);
        },
        queryState: function(km) {
            var nodes = km.getSelectedNodes();

            if (nodes.length === 0) {
                return -1;
            }
            var image = false;
            utils.each(nodes, function(i, n) {
                if (n.getData('image')) {
                    image = true;
                    return false;
                }
            });
            if (image) {
                return 0;
            }
            return -1;
        }
    });

    var ImageRenderer = kity.createClass('ImageRenderer', {
        base: KityMinder.Renderer,

        create: function(node) {
            return new kity.Image(node.getData('image'));
        },

        shouldRender: function(node) {
            return node.getData('image');
        },

        update: function(image, node, box) {
            var url = node.getData('image');
            var size = node.getData('imageSize');
            var spaceTop = node.getStyle('space-top');

            if (!size) return;

            var x = box.cx - size.width / 2;
            var y = box.y - size.height - spaceTop;

            image
                .setUrl(url)
                .setX(x | 0)
                .setY(y | 0)
                .setWidth(size.width | 0)
                .setHeight(size.height | 0);

            return new kity.Box(x | 0, y | 0, size.width | 0, size.height | 0);
        }
    });

    return {
        'defaultOptions': {
            'maxImageWidth': 200,
            'maxImageHeight': 200
        },
        'commands': {
            'image': ImageCommand,
            'removeimage': RemoveImageCommand
        },
        'renderers': {
            'top': ImageRenderer
        }
    };
});<commit_msg>修复插入图片不能进行undo/redo的问题<commit_after>KityMinder.registerModule('image', function() {
    function loadImageSize(url, callback) {
        var img = document.createElement('img');
        img.onload = function() {
            callback(img.width, img.height);
        };
        img.onerror = function() {
            callback(null);
        };
        img.src = url;
    }

    function fitImageSize(width, height, maxWidth, maxHeight) {
        var ratio = width / height,
            fitRatio = maxWidth / maxHeight;

        // 宽高比大于最大尺寸的宽高比，以宽度为标准适应
        if (ratio > fitRatio && width > maxWidth) {
            width = maxWidth;
            height = maxWidth / ratio;
        } else if (height > maxHeight) {
            height = maxHeight;
            width = maxHeight / ratio;
        }

        return {
            width: width | 0,
            height: height | 0
        };
    }

    var ImageCommand = kity.createClass('ImageCommand', {
        base: Command,

        execute: function(km, url) {
            var nodes = km.getSelectedNodes();

            loadImageSize(url, function(width, height) {
                if (!width) return;
                utils.each(nodes, function(i, n) {
                    var size = fitImageSize(
                        width, height,
                        km.getOptions('maxImageWidth'),
                        km.getOptions('maxImageHeight'));
                    n.setData('image', url);
                    n.setData('imageSize', size);
                    n.render();
                });
                km.fire("saveScene");
                km.layout(300);
            });

        },
        queryState: function(km) {
            var nodes = km.getSelectedNodes(),
                result = 0;
            if (nodes.length === 0) {
                return -1;
            }
            utils.each(nodes, function(i, n) {
                if (n && n.getData('image')) {
                    result = 0;
                    return false;
                }
            });
            return result;
        },
        queryValue: function(km) {
            var node = km.getSelectedNode();
            return node.getData('image');
        }
    });

    var RemoveImageCommand = kity.createClass('RemoveImageCommand', {
        base: Command,

        execute: function(km) {
            var nodes = km.getSelectedNodes();
            utils.each(nodes, function(i, n) {
                n.setData('image').render();
            });
            km.layout(300);
        },
        queryState: function(km) {
            var nodes = km.getSelectedNodes();

            if (nodes.length === 0) {
                return -1;
            }
            var image = false;
            utils.each(nodes, function(i, n) {
                if (n.getData('image')) {
                    image = true;
                    return false;
                }
            });
            if (image) {
                return 0;
            }
            return -1;
        }
    });

    var ImageRenderer = kity.createClass('ImageRenderer', {
        base: KityMinder.Renderer,

        create: function(node) {
            return new kity.Image(node.getData('image'));
        },

        shouldRender: function(node) {
            return node.getData('image');
        },

        update: function(image, node, box) {
            var url = node.getData('image');
            var size = node.getData('imageSize');
            var spaceTop = node.getStyle('space-top');

            if (!size) return;

            var x = box.cx - size.width / 2;
            var y = box.y - size.height - spaceTop;

            image
                .setUrl(url)
                .setX(x | 0)
                .setY(y | 0)
                .setWidth(size.width | 0)
                .setHeight(size.height | 0);

            return new kity.Box(x | 0, y | 0, size.width | 0, size.height | 0);
        }
    });

    return {
        'defaultOptions': {
            'maxImageWidth': 200,
            'maxImageHeight': 200
        },
        'commands': {
            'image': ImageCommand,
            'removeimage': RemoveImageCommand
        },
        'renderers': {
            'top': ImageRenderer
        }
    };
});<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */

var express = require('express')
  , stylus = require('stylus')
  , nib = require('nib')
  , sio = require('../../lib/socket.io');

/**
 * App.
 */

var app = express.createServer();

/**
 * App configuration.
 */

app.configure(function () {
  app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }))
  app.use(express.static(__dirname + '/public'));
  app.set('views', __dirname);
  app.set('view engine', 'jade');

  function compile (str, path) {
    return stylus(str)
      .set('filename', path)
      .use(nib());
  };
});

/**
 * App routes.
 */

app.get('/', function (req, res) {
  res.render('index', { layout: false });
});

/**
 * App listen.
 */

app.listen(3000, function () {
  var addr = app.address();
  console.log('   app listening on http://' + addr.address + ':' + addr.port);
});

/**
 * Socket.IO server (single process only)
 */

var io = sio.listen(app)
  , nicknames = {};

io.sockets.on('connection', function (socket) {
  socket.on('user message', function (msg) {
    socket.broadcast.emit('user message', socket.nickname, msg);
  });

  socket.on('nickname', function (nick, fn) {
    if (nicknames[nick]) {
      fn(true);
    } else {
      fn(false);
      nicknames[nick] = socket.nickname = nick;
      socket.broadcast.emit('announcement', nick + ' connected');
      io.sockets.emit('nicknames', nicknames);
    }
  });

  socket.on('disconnect', function () {
    if (!socket.nickname) return;

    delete nicknames[socket.nickname];
    socket.broadcast.emit('announcement', socket.nickname + ' disconnected');
    socket.broadcast.emit('nicknames', nicknames);
  });
});
<commit_msg>add semicolon<commit_after>/**
 * Module dependencies.
 */

var express = require('express')
  , stylus = require('stylus')
  , nib = require('nib')
  , sio = require('../../lib/socket.io');

/**
 * App.
 */

var app = express.createServer();

/**
 * App configuration.
 */

app.configure(function () {
  app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }));
  app.use(express.static(__dirname + '/public'));
  app.set('views', __dirname);
  app.set('view engine', 'jade');

  function compile (str, path) {
    return stylus(str)
      .set('filename', path)
      .use(nib());
  };
});

/**
 * App routes.
 */

app.get('/', function (req, res) {
  res.render('index', { layout: false });
});

/**
 * App listen.
 */

app.listen(3000, function () {
  var addr = app.address();
  console.log('   app listening on http://' + addr.address + ':' + addr.port);
});

/**
 * Socket.IO server (single process only)
 */

var io = sio.listen(app)
  , nicknames = {};

io.sockets.on('connection', function (socket) {
  socket.on('user message', function (msg) {
    socket.broadcast.emit('user message', socket.nickname, msg);
  });

  socket.on('nickname', function (nick, fn) {
    if (nicknames[nick]) {
      fn(true);
    } else {
      fn(false);
      nicknames[nick] = socket.nickname = nick;
      socket.broadcast.emit('announcement', nick + ' connected');
      io.sockets.emit('nicknames', nicknames);
    }
  });

  socket.on('disconnect', function () {
    if (!socket.nickname) return;

    delete nicknames[socket.nickname];
    socket.broadcast.emit('announcement', socket.nickname + ' disconnected');
    socket.broadcast.emit('nicknames', nicknames);
  });
});
<|endoftext|>


<commit_before>const {Router} = require('express');
const body = require('body-parser');

const {getData, handleError} = require('./util');

const Cookies = require('./cookies');
const Tokens = require('./tokens');
const Users = require('./users');
const Subscriptions = require('./subscriptions');

module.exports = function MembersApi({
    authConfig: {
        issuer,
        privateKey,
        publicKey,
        sessionSecret,
        ssoOrigin,
        accessControl
    },
    paymentConfig,
    createMember,
    validateMember,
    updateMember,
    getMember,
    deleteMember,
    listMembers,
    sendEmail,
    siteConfig
}) {
    const {encodeToken, decodeToken, getPublicKeys} = Tokens({privateKey, publicKey, issuer});

    let subscriptions = new Subscriptions(paymentConfig);

    let users = Users({
        subscriptions,
        createMember,
        updateMember,
        getMember,
        deleteMember,
        validateMember,
        sendEmail,
        encodeToken,
        listMembers,
        decodeToken
    });

    const apiRouter = Router();

    apiRouter.use(body.json());

    /* session */
    const {getCookie, setCookie, removeCookie} = Cookies(sessionSecret);

    function validateAccess({audience, origin}) {
        const audienceLookup = accessControl[origin] || {
            [origin]: accessControl['*']
        };

        const tokenSettings = audienceLookup[audience];

        if (tokenSettings) {
            return Promise.resolve(tokenSettings);
        }

        return Promise.reject();
    }

    /* token */
    apiRouter.post('/token', getData('audience'), (req, res) => {
        const {signedin} = getCookie(req);
        if (!signedin) {
            res.writeHead(401, {
                'Set-Cookie': removeCookie()
            });
            return res.end();
        }

        const {audience, origin} = req.data;

        validateAccess({audience, origin})
            .then(({tokenLength}) => {
                return users.get({id: signedin})
                    .then(member => encodeToken({
                        sub: member.id,
                        plans: member.plans,
                        exp: tokenLength,
                        aud: audience
                    }));
            })
            .then(token => res.end(token))
            .catch(handleError(403, res));
    });

    apiRouter.get('/config', (req, res) => {
        subscriptions.getAdapters()
            .then((adapters) => {
                return Promise.all(adapters.map((adapter) => {
                    return subscriptions.getPublicConfig(adapter);
                }));
            })
            .then(paymentConfig => res.json({
                paymentConfig,
                issuer,
                siteConfig
            }))
            .catch(handleError(500, res));
    });

    /* security */
    function ssoOriginCheck(req, res, next) {
        if (!req.data.origin || req.data.origin !== ssoOrigin) {
            res.writeHead(403);
            return res.end();
        }
        next();
    }

    /* subscriptions */
    apiRouter.post('/subscription', getData('adapter', 'plan', 'stripeToken', {name: 'coupon', required: false}), ssoOriginCheck, (req, res) => {
        const {signedin} = getCookie(req);
        if (!signedin) {
            res.writeHead(401, {
                'Set-Cookie': removeCookie()
            });
            return res.end();
        }

        const {plan, adapter, stripeToken, coupon} = req.data;

        subscriptions.getAdapters()
            .then((adapters) => {
                if (!adapters.includes(adapter)) {
                    throw new Error('Invalid adapter');
                }
            })
            .then(() => users.get({id: signedin}))
            .then((member) => {
                return subscriptions.createSubscription(member, {
                    adapter,
                    plan,
                    stripeToken,
                    coupon
                });
            })
            .then(() => {
                res.end();
            })
            .catch(handleError(500, res));
    });

    /* users, token, emails */
    apiRouter.post('/request-password-reset', getData('email'), ssoOriginCheck, (req, res) => {
        const {email} = req.data;

        users.requestPasswordReset({email}).then(() => {
            res.writeHead(200);
            res.end();
        }).catch(handleError(500, res));
    });

    /* users, token */
    apiRouter.post('/reset-password', getData('token', 'password'), ssoOriginCheck, (req, res) => {
        const {token, password} = req.data;

        users.resetPassword({token, password}).then((member) => {
            res.writeHead(200, {
                'Set-Cookie': setCookie(member)
            });
            res.end();
        }).catch(handleError(401, res));
    });

    /* users, email */
    apiRouter.post('/signup', getData('name', 'email', 'password'), ssoOriginCheck, (req, res) => {
        const {name, email, password} = req.data;

        // @TODO this should attempt to reset password before creating member
        users.create({name, email, password}).then((member) => {
            res.writeHead(200, {
                'Set-Cookie': setCookie(member)
            });
            res.end();
        }).catch(handleError(400, res));
    });

    /* users, session */
    apiRouter.post('/signin', getData('email', 'password'), ssoOriginCheck, (req, res) => {
        const {email, password} = req.data;

        users.validate({email, password}).then((member) => {
            res.writeHead(200, {
                'Set-Cookie': setCookie(member)
            });
            res.end();
        }).catch(handleError(401, res));
    });

    /* session */
    apiRouter.post('/signout', getData(), (req, res) => {
        res.writeHead(200, {
            'Set-Cookie': removeCookie()
        });
        res.end();
    });

    /* http */
    const staticRouter = Router();
    staticRouter.get('/gateway', (req, res) => {
        res.status(200).send(`
            <script>
                window.membersApiUrl = "${issuer}";
            </script>
            <script src="bundle.js"></script>
        `);
    });
    staticRouter.get('/bundle.js', (req, res) => {
        res.status(200).sendFile(require('path').join(__dirname, './static/gateway/bundle.js'));
    });

    const apiInstance = {
        staticRouter,
        apiRouter
    };
    apiInstance.members = users;
    apiInstance.getPublicKeys = getPublicKeys;
    apiInstance.getPublicConfig = function () {
        return Promise.resolve({
            publicKey,
            issuer
        });
    };
    apiInstance.getMember = function (id, token) {
        return decodeToken(token).then(() => {
            return users.get({id});
        });
    };
    apiInstance.reconfigureSettings = function (data) {
        subscriptions = new Subscriptions(data.paymentConfig);
        users = Users({
            subscriptions,
            createMember,
            updateMember,
            getMember,
            deleteMember,
            validateMember,
            sendEmail,
            encodeToken,
            listMembers,
            decodeToken
        });
        siteConfig = data.siteConfig;
    };

    return apiInstance;
};
<commit_msg>Updated require paths for local modules<commit_after>const {Router} = require('express');
const body = require('body-parser');

const {getData, handleError} = require('./lib/util');

const Cookies = require('./lib/cookies');
const Tokens = require('./lib/tokens');
const Users = require('./lib/users');
const Subscriptions = require('./lib/subscriptions');

module.exports = function MembersApi({
    authConfig: {
        issuer,
        privateKey,
        publicKey,
        sessionSecret,
        ssoOrigin,
        accessControl
    },
    paymentConfig,
    createMember,
    validateMember,
    updateMember,
    getMember,
    deleteMember,
    listMembers,
    sendEmail,
    siteConfig
}) {
    const {encodeToken, decodeToken, getPublicKeys} = Tokens({privateKey, publicKey, issuer});

    let subscriptions = new Subscriptions(paymentConfig);

    let users = Users({
        subscriptions,
        createMember,
        updateMember,
        getMember,
        deleteMember,
        validateMember,
        sendEmail,
        encodeToken,
        listMembers,
        decodeToken
    });

    const apiRouter = Router();

    apiRouter.use(body.json());

    /* session */
    const {getCookie, setCookie, removeCookie} = Cookies(sessionSecret);

    function validateAccess({audience, origin}) {
        const audienceLookup = accessControl[origin] || {
            [origin]: accessControl['*']
        };

        const tokenSettings = audienceLookup[audience];

        if (tokenSettings) {
            return Promise.resolve(tokenSettings);
        }

        return Promise.reject();
    }

    /* token */
    apiRouter.post('/token', getData('audience'), (req, res) => {
        const {signedin} = getCookie(req);
        if (!signedin) {
            res.writeHead(401, {
                'Set-Cookie': removeCookie()
            });
            return res.end();
        }

        const {audience, origin} = req.data;

        validateAccess({audience, origin})
            .then(({tokenLength}) => {
                return users.get({id: signedin})
                    .then(member => encodeToken({
                        sub: member.id,
                        plans: member.plans,
                        exp: tokenLength,
                        aud: audience
                    }));
            })
            .then(token => res.end(token))
            .catch(handleError(403, res));
    });

    apiRouter.get('/config', (req, res) => {
        subscriptions.getAdapters()
            .then((adapters) => {
                return Promise.all(adapters.map((adapter) => {
                    return subscriptions.getPublicConfig(adapter);
                }));
            })
            .then(paymentConfig => res.json({
                paymentConfig,
                issuer,
                siteConfig
            }))
            .catch(handleError(500, res));
    });

    /* security */
    function ssoOriginCheck(req, res, next) {
        if (!req.data.origin || req.data.origin !== ssoOrigin) {
            res.writeHead(403);
            return res.end();
        }
        next();
    }

    /* subscriptions */
    apiRouter.post('/subscription', getData('adapter', 'plan', 'stripeToken', {name: 'coupon', required: false}), ssoOriginCheck, (req, res) => {
        const {signedin} = getCookie(req);
        if (!signedin) {
            res.writeHead(401, {
                'Set-Cookie': removeCookie()
            });
            return res.end();
        }

        const {plan, adapter, stripeToken, coupon} = req.data;

        subscriptions.getAdapters()
            .then((adapters) => {
                if (!adapters.includes(adapter)) {
                    throw new Error('Invalid adapter');
                }
            })
            .then(() => users.get({id: signedin}))
            .then((member) => {
                return subscriptions.createSubscription(member, {
                    adapter,
                    plan,
                    stripeToken,
                    coupon
                });
            })
            .then(() => {
                res.end();
            })
            .catch(handleError(500, res));
    });

    /* users, token, emails */
    apiRouter.post('/request-password-reset', getData('email'), ssoOriginCheck, (req, res) => {
        const {email} = req.data;

        users.requestPasswordReset({email}).then(() => {
            res.writeHead(200);
            res.end();
        }).catch(handleError(500, res));
    });

    /* users, token */
    apiRouter.post('/reset-password', getData('token', 'password'), ssoOriginCheck, (req, res) => {
        const {token, password} = req.data;

        users.resetPassword({token, password}).then((member) => {
            res.writeHead(200, {
                'Set-Cookie': setCookie(member)
            });
            res.end();
        }).catch(handleError(401, res));
    });

    /* users, email */
    apiRouter.post('/signup', getData('name', 'email', 'password'), ssoOriginCheck, (req, res) => {
        const {name, email, password} = req.data;

        // @TODO this should attempt to reset password before creating member
        users.create({name, email, password}).then((member) => {
            res.writeHead(200, {
                'Set-Cookie': setCookie(member)
            });
            res.end();
        }).catch(handleError(400, res));
    });

    /* users, session */
    apiRouter.post('/signin', getData('email', 'password'), ssoOriginCheck, (req, res) => {
        const {email, password} = req.data;

        users.validate({email, password}).then((member) => {
            res.writeHead(200, {
                'Set-Cookie': setCookie(member)
            });
            res.end();
        }).catch(handleError(401, res));
    });

    /* session */
    apiRouter.post('/signout', getData(), (req, res) => {
        res.writeHead(200, {
            'Set-Cookie': removeCookie()
        });
        res.end();
    });

    /* http */
    const staticRouter = Router();
    staticRouter.get('/gateway', (req, res) => {
        res.status(200).send(`
            <script>
                window.membersApiUrl = "${issuer}";
            </script>
            <script src="bundle.js"></script>
        `);
    });
    staticRouter.get('/bundle.js', (req, res) => {
        res.status(200).sendFile(require('path').join(__dirname, './gateway/bundle.js'));
    });

    const apiInstance = {
        staticRouter,
        apiRouter
    };
    apiInstance.members = users;
    apiInstance.getPublicKeys = getPublicKeys;
    apiInstance.getPublicConfig = function () {
        return Promise.resolve({
            publicKey,
            issuer
        });
    };
    apiInstance.getMember = function (id, token) {
        return decodeToken(token).then(() => {
            return users.get({id});
        });
    };
    apiInstance.reconfigureSettings = function (data) {
        subscriptions = new Subscriptions(data.paymentConfig);
        users = Users({
            subscriptions,
            createMember,
            updateMember,
            getMember,
            deleteMember,
            validateMember,
            sendEmail,
            encodeToken,
            listMembers,
            decodeToken
        });
        siteConfig = data.siteConfig;
    };

    return apiInstance;
};
<|endoftext|>


<commit_before>// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * Extracted information from GitHub links.

 * @typedef {Object} GitHubLink
 * @property {string} owner - User or organization that owns the repository.
 * @property {string} repo - Name of the repository.
 * @property {string} action - Action to dispatch.
 * @property {number} id - ID of the issue or pull request.
 */

/**
 * Retrieves the current message given an add-on event.
 * @param {Event} event - Add-on event
 * @return {Message}
 */
function getCurrentMessage(event) {
  var accessToken = event.messageMetadata.accessToken;
  var messageId = event.messageMetadata.messageId;
  GmailApp.setCurrentMessageAccessToken(accessToken);
  return GmailApp.getMessageById(messageId);
}

/**
 * Extracts pull requests or issues from one or more messages.
 *
 * @param {string|string[]} messageBodies - message bodies to scan for links.
 * @return {GitHubLink[]} extracted link information, empty array if none found.
 */
function extractGitHubLinks(messageBodies) {
  var bodies = _.castArray(messageBodies);
  var links = [];
  _.each(bodies, function(body) {
    extractGitHubLinksFromText_(body, links);
  });
  return _.uniqBy(links, function(item) {
    return item.owner + item.repo + item.id;
  });
}

/**
 * Extracts pull requests or issues from text.
 *
 * @param {string|string[]} text - raw text to scan for links.
 * @return {GitHubLink[]} extracted link information, empty array if none found.
 */
function extractGitHubLinksFromText_(text, appendTo) {
  var re = /https:\/\/github.com\/(.+?)(?<!<)\/(?!>)(.+?)(?<!<)\/(?!>)(issues|pull)(?<!<)\/(?!>)(\d+)/gi;
  while ((match = re.exec(text)) !== null) {
    var type = match[3].replace(/<.+?>/g,'');
    appendTo.push({
      owner: match[1].replace(/<.+?>/g,''),
      repo: match[2].replace(/<.+?>/g,''),
      id: parseInt(match[4].replace(/<.+?>/g,'')),
      type: type
    });
  }
}
<commit_msg>Slight refactor to improve readability<commit_after>// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * Extracted information from GitHub links.

 * @typedef {Object} GitHubLink
 * @property {string} owner - User or organization that owns the repository.
 * @property {string} repo - Name of the repository.
 * @property {string} action - Action to dispatch.
 * @property {number} id - ID of the issue or pull request.
 */

/**
 * Retrieves the current message given an add-on event.
 * @param {Event} event - Add-on event
 * @return {Message}
 */
function getCurrentMessage(event) {
  var accessToken = event.messageMetadata.accessToken;
  var messageId = event.messageMetadata.messageId;
  GmailApp.setCurrentMessageAccessToken(accessToken);
  return GmailApp.getMessageById(messageId);
}

/**
 * Extracts pull requests or issues from one or more messages.
 *
 * @param {string|string[]} messageBodies - message bodies to scan for links.
 * @return {GitHubLink[]} extracted link information, empty array if none found.
 */
function extractGitHubLinks(messageBodies) {
  var bodies = _.castArray(messageBodies);
  var links = [];
  _.each(bodies, function(body) {
    extractGitHubLinksFromText_(body, links);
  });
  return _.uniqBy(links, function(item) {
    return item.owner + item.repo + item.id;
  });
}

/**
 * Extracts pull requests or issues from text.
 *
 * @param {string|string[]} text - raw text to scan for links.
 * @return {GitHubLink[]} extracted link information, empty array if none found.
 */
function extractGitHubLinksFromText_(text, appendTo) {
  var re = /https:\/\/github.com\/(.+?)(?<!<)\/(?!>)(.+?)(?<!<)\/(?!>)(issues|pull)(?<!<)\/(?!>)(\d+)/gi;
  while ((match = re.exec(text)) !== null) {
    var type = stripHtmlTags(match[3]);
    appendTo.push({
      owner: stripHtmlTags(match[1]),
      repo: stripHtmlTags(match[2]),
      id: parseInt(stripHtmlTags(match[4])),
      type: type
    });
  }
}
/**
 * Strips HTML tags from a string. Not a general purpose implementation,
 * just removes anything encased in <>.
 *
 * @param {string} text - text to strip tags from.
 * @return {string}
 */
function stripHtmlTags(str) {
  return str.replace(/<.+?>/g,'');
}
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import {NavLink} from 'fluxible-router';
import {connectToStores} from 'fluxible-addons-react';
//import PresentationSlideList from './PresentationSlideList';
import PresentationSlide from './PresentationSlide';
import DeckTreeStore from '../../../stores/DeckTreeStore';
import PresentationStore from '../../../stores/PresentationStore';
import loadPresentation from '../../../actions/loadPresentation';

let playerCss = {
    height: '100%',
    fontSize: '100%',
    position: 'absolute',
    top: '0',
};

let clearStyle = {
    clear: 'both'
};

class Presentation extends React.Component{
    constructor(props){
        super(props);
        this.playerCss = playerCss;
        this.slides = [];
        this.startingSlide = this.props.PresentationStore.selector.sid;
        this.deck = this.props.PresentationStore.selector.id;
    }

    componentDidMount(){
        if(process.env.BROWSER){
            let style = require('../../../bower_components/reveal.js/css/reveal.css');
            //Hide the header and footer
            $('.ui.footer.sticky.segment').css({'display': 'none'});
            $('.ui.inverted.blue.menu, .ui.inverted.menu .blue.active.item').css({'display': 'none'});
            $('.ui.footer.sticky.segment').attr({'aria-hidden': 'hidden', 'hidden': 'hidden'});
            $('.ui.inverted.blue.menu, .ui.inverted.menu .blue.active.item').attr({'aria-hidden': 'hidden', 'hidden': 'hidden'});

            let s = this.props.PresentationStore.theme;

            if(!s){
                s = 'black';
            }
            require('../../../bower_components/reveal.js/css/theme/' + s + '.css');

            Reveal.initialize({
                history: true,
                dependencies: [
                    { src: '/bower_components/reveal.js/plugin/notes/notes.js', async: true }
                ]
            });

        }
    }

    componentDidUpdate(){

    }
    render(){
        this.slides = this.getSlides();
        return(

        				{this.slides}

        );
    }

    getSlides(){
        let slides = this.props.PresentationStore.content;

        let returnList = [];
        if(slides){
            for (let i = 0; i < slides.length; i++) {
                let slide = slides[i];
              //  console.log(slide);
                let content = slide.title + slide.content + '' + slide.speakernotes + '';
                returnList.push(<PresentationSlide content={content} key={slide.id} id={'slide-' + slide.id} />);
            }
            return returnList;

        }
        else{
            return (
);
        }
    }

}

Presentation.contextTypes = {
    executeAction: React.PropTypes.func.isRequired
};

Presentation = connectToStores(Presentation, [PresentationStore], (context, props) => {
    return {
        PresentationStore: context.getStore(PresentationStore).getState()
    };
});

export default Presentation;
<commit_msg>SWIK134 hidden slides until player is running<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import {NavLink} from 'fluxible-router';
import {connectToStores} from 'fluxible-addons-react';
//import PresentationSlideList from './PresentationSlideList';
import PresentationSlide from './PresentationSlide';
import DeckTreeStore from '../../../stores/DeckTreeStore';
import PresentationStore from '../../../stores/PresentationStore';
import loadPresentation from '../../../actions/loadPresentation';

let playerCss = {
    display: 'none',
    height: '100%',
    fontSize: '100%',
    position: 'absolute',
    top: '0',
};

let clearStyle = {
    clear: 'both'
};

class Presentation extends React.Component{
    constructor(props){
        super(props);
        this.playerCss = playerCss;
        this.slides = [];
        this.startingSlide = this.props.PresentationStore.selector.sid;
        this.deck = this.props.PresentationStore.selector.id;
        this.revealDiv = null;
    }

    componentDidMount(){
        if(process.env.BROWSER){
             //loading reveal style
            require('../../../bower_components/reveal.js/css/reveal.css');

            //Hide the header and footer
            $('.ui.footer.sticky.segment').css({'display': 'none'});
            $('.ui.inverted.blue.menu, .ui.inverted.menu .blue.active.item').css({'display': 'none'});
            $('.ui.footer.sticky.segment').attr({'aria-hidden': 'hidden', 'hidden': 'hidden'});
            $('.ui.inverted.blue.menu, .ui.inverted.menu .blue.active.item').attr({'aria-hidden': 'hidden', 'hidden': 'hidden'});

            let styleName = this.props.PresentationStore.theme;

            if(!styleName){
                styleName = 'black';
            }

            //loading selected style for presentation
            require('../../../bower_components/reveal.js/css/theme/' + styleName + '.css');

            this.revealDiv.style.display = 'inline';

            Reveal.initialize({
                history: true,
                dependencies: [
                    { src: '/bower_components/reveal.js/plugin/notes/notes.js', async: true }
                ]
            });

        }
    }

    componentDidUpdate(){

    }
    render(){
        this.slides = this.getSlides();
        return(

 this.revealDiv = refToDiv} >

        			     	{this.slides}

        );
    }

    getSlides(){
        let slides = this.props.PresentationStore.content;

        let returnList = [];
        if(slides){
            for (let i = 0; i < slides.length; i++) {
                let slide = slides[i];
              //  console.log(slide);
                let content = slide.title + slide.content + '' + slide.speakernotes + '';
                returnList.push(<PresentationSlide content={content} key={slide.id} id={'slide-' + slide.id} />);
            }
            return returnList;

        }
        else{
            return (
);
        }
    }

}

Presentation.contextTypes = {
    executeAction: React.PropTypes.func.isRequired
};

Presentation = connectToStores(Presentation, [PresentationStore], (context, props) => {
    return {
        PresentationStore: context.getStore(PresentationStore).getState()
    };
});

export default Presentation;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Text } from 'react-native';

import styles from '../../styles/styles.ios';
import InPageImage from '../inPageImage';
import AnimalText from '../animalText';
import AnimalTemplate from '../animalTemplate';

const IMAGES = [
  require('../../images/animals/levhartCejlonsky/01.jpg'),
  require('../../images/animals/levhartCejlonsky/02.jpg'),
  require('../../images/animals/levhartCejlonsky/03.jpg'),
];

const THUMBNAILS = [
  require('../../images/animals/levhartCejlonsky/01-thumb.jpg'),
  require('../../images/animals/levhartCejlonsky/02-thumb.jpg'),
  require('../../images/animals/levhartCejlonsky/03-thumb.jpg'),
];

var AnimalDetail = React.createClass({
  render() {
    return (
      <AnimalTemplate firstIndex={[0]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator}>
        <AnimalText>
          Ahoj kamarádi,
        </AnimalText>
        <AnimalText>
          jmenuji se Daan a jsem levhart cejlonský. Hned na úvod vám musím říct, že já a moje kamarádka Nayana patříme k jednomu z největších druhů levhartů. Na Srí Lance, odkud pocházíme, jsme dokonce největší kočkovité šelmy. Přesto je náš druh ohrožený a počet našich kamarádů stále klesá. V přírodě se dožíváme 10 let, v zoo až 23 roků.
        </AnimalText>
      <AnimalTemplate firstIndex={[1]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Naši kluci váží kolem 60 kilogramů a holky okolo 30 kilogramů. Proto můžete mě a Nayanu snadno rozlišit. Náš ocásek může mít až jeden metr. Omotal by vás celé kolem dokola jako šála. A je krásně zbarvený, stejně jako zbytek našeho kožíšku. Celý ho máme posetý skvrnami, abychom se mohli v divočině schovávat a nikdo nás neviděl. Každý z nás má díky genetice skvrny trošku jiné. Tak jako vy máte třeba vlasy po tatínkovi a oči po mamince, my dědíme po svých rodičích skvrny.
        </AnimalText>
      <AnimalTemplate firstIndex={[2]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Oplýváme výborným sluchem, zrakem i čichem. Umíme moc dobře plavat, vyskočíme až nad hlavy vašich rodičů a běhat dokážeme stejně rychle jako auto jedoucí v obci. Díky tomu jsme skvělí lovci – dokážeme ulovit nejen malé ptáky a plazy, ale i větší zvířata, jako jsou třeba jelen, divoké prase, nebo dokonce dospělý buvol. Nejraději lovíme v noci.
        </AnimalText>
        <AnimalText>
          Jsme samotáři, proto jsme tady ve výběhu jen my dva, to abychom se moc nehádali.
        </AnimalText>
        <AnimalText>
          Mějte se pěkně a přijďte se na nás zase podívat.
        </AnimalText>
      </AnimalTemplate>
    );
  }
});

module.exports = AnimalDetail;
<commit_msg>Update levhartCejlonsky.child.js<commit_after>import React, { Component } from 'react';
import { Text } from 'react-native';

import styles from '../../styles/styles.ios';
import InPageImage from '../inPageImage';
import AnimalText from '../animalText';
import AnimalTemplate from '../animalTemplate';

const IMAGES = [
  require('../../images/animals/levhartCejlonsky/01.jpg'),
  require('../../images/animals/levhartCejlonsky/02.jpg'),
  require('../../images/animals/levhartCejlonsky/03.jpg'),
];

const THUMBNAILS = [
  require('../../images/animals/levhartCejlonsky/01-thumb.jpg'),
  require('../../images/animals/levhartCejlonsky/02-thumb.jpg'),
  require('../../images/animals/levhartCejlonsky/03-thumb.jpg'),
];

var AnimalDetail = React.createClass({
  render() {
    return (
      <AnimalTemplate firstIndex={[0]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator}>
        <AnimalText>
          Ahoj kamarádi,
        </AnimalText>
        <AnimalText>
          jmenuji se Daan a jsem levhart cejlonský. Hned na úvod vám musím říct, že já a moje kamarádka Nayana patříme k jednomu z největších druhů levhartů. Na Srí Lance, odkud pocházíme, jsme dokonce největší kočkovité šelmy. Přesto je náš druh ohrožený a počet našich kamarádů stále klesá. V přírodě se dožíváme 10 let, v zoo až 23 roků.
        </AnimalText>
      <InPageImage indexes={[1]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Naši kluci váží kolem 60 kilogramů a holky okolo 30 kilogramů. Proto můžete mě a Nayanu snadno rozlišit. Náš ocásek může mít až jeden metr. Omotal by vás celé kolem dokola jako šála. A je krásně zbarvený, stejně jako zbytek našeho kožíšku. Celý ho máme posetý skvrnami, abychom se mohli v divočině schovávat a nikdo nás neviděl. Každý z nás má díky genetice skvrny trošku jiné. Tak jako vy máte třeba vlasy po tatínkovi a oči po mamince, my dědíme po svých rodičích skvrny.
        </AnimalText>
      <InPageImage indexes={[2]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Oplýváme výborným sluchem, zrakem i čichem. Umíme moc dobře plavat, vyskočíme až nad hlavy vašich rodičů a běhat dokážeme stejně rychle jako auto jedoucí v obci. Díky tomu jsme skvělí lovci – dokážeme ulovit nejen malé ptáky a plazy, ale i větší zvířata, jako jsou třeba jelen, divoké prase, nebo dokonce dospělý buvol. Nejraději lovíme v noci.
        </AnimalText>
        <AnimalText>
          Jsme samotáři, proto jsme tady ve výběhu jen my dva, to abychom se moc nehádali.
        </AnimalText>
        <AnimalText>
          Mějte se pěkně a přijďte se na nás zase podívat.
        </AnimalText>
      </AnimalTemplate>
    );
  }
});

module.exports = AnimalDetail;
<|endoftext|>


<commit_before>/**
 * Created by Fine on 2016/12/4.
 */

var crossTable = {
    table: function(args){
        var data = args.data,
            chartConfig = args.chartConfig,
            tall = args.tall,
            pageDataNum = 20,
            container = args.container;
        var html = "

",
            colContent = "
";
        for (var i = 0; i < chartConfig.groups.length; i++) {
            var colspan = 1;
            var colList = [];
            for (var t = 0; t < chartConfig.keys.length; t++) {
                colContent += "

	";
            }
            for (var y = chartConfig.keys.length; y < data[i].length; y++) {
                if ((data[i][y + 1]) && (data[i][y].data == data[i][y + 1].data)) {
                    if(i > 0) {
                        var noEqual = false;
                        for (var s = i - 1; s > -1; i--) {
                            if (data[s][y].data != data[s][y + 1].data) {
                                noEqual = true;
                                break;
                            }
                        }
                        if (noEqual) {
                            colList.push({
                                data: data[i][y].data,
                                colSpan: colspan,
                                property: data[i][y].property
                            });
                            colspan = 1;
                        }
                        else {
                            colspan++;
                        }
                        // if (data[i - 1][y].data == data[i - 1][y + 1].data) {
                        //     colspan++
                        // }
                        // else {
                        //     colList.push({
                        //         data: data[i][y].data,
                        //         colSpan: colspan,
                        //         property: data[i][y].property
                        //     });
                        //     colspan = 1;
                        // }
                    }
                    else if(i == 0) {
                        colspan++;
                    }
                }
                else {
                    data[i][y] != data[i][y - 1] ? colList.push({
                        data: data[i][y].data,
                        colSpan: colspan,
                        property: data[i][y].property
                    }) : null;
                    colspan = 1;
                }
            }
            for (var c = 0; c < colList.length; c++) {
                colContent += colList[c].colSpan > 1 ? "
" + colList[c].data + "
	" :
                "
" + colList[c].data + "
	";
            }
            colContent += "

";
        }
        for (var k = 0; k < data[chartConfig.groups.length].length; k++) {
            colContent += "
" + data[chartConfig.groups.length][k].data + "
	";
        }
        html += colContent + "

";
        var dataPage = this.paginationProcessData(data, chartConfig.groups.length + 1, pageDataNum);
        var trDom = this.render(dataPage[0], chartConfig);
        html = html + trDom + "

";
        var PaginationDom = "

";
        var optionDom = "<select class='optionNum'><option value='20'>20</option><option value='50'>50</option><option value='100'>100</option><option value=''>150</option></select>";
        var operate = "
<button class='exportBnt'>export</button>" + optionDom + "
";

        $(container).html(operate);
        $(container).append("
" + html + "
");
        $(container).append(PaginationDom);
        dataPage.map(function(d, i){
            $('.page>ul').append('
' + (i + 1) + '
');
        });
        this.clickPageNum(dataPage, chartConfig);
        this.selectDataNum(data, chartConfig.groups.length + 1, chartConfig);
        this.export();
    },
    paginationProcessData: function (data, num, pageDataNum) {
        var length = data.length - num;
        var t = length % pageDataNum;
        var rest = parseInt(length / pageDataNum);
        var page;
        var pageData = [];
        t == 0 ? page = rest : page = rest + 1;
        for (var i = 1; i < page + 1; i++) {
            var partData = [];
            if (i == page) {
                for (var j = (i - 1) * pageDataNum + num; j < pageDataNum * (page - 1) + t + num; j++) {
                    partData.push(data[j]);
                }
            } else {
                for (var j = (i - 1) * pageDataNum + num; j < pageDataNum * i + num; j++) {
                    partData.push(data[j]);
                }
            }
            pageData.push(partData);
        }
        return pageData;
    },
    render: function (data, chartConfig) {
        var html = '';
        for (var r = 0; r < chartConfig.keys.length; r++) {
            for(var n = 1; n < data.length; n++){
                var node = data[n][r].data;
                if (r > 0) {
                    var parent = data[n][r - 1].data;
                    var next;
                    n > 0 ? next = data[n - 1][r - 1].data : null;
                    (node == data[n - 1][r].data && parent == next) ? data[n][r] = {
                        data: data[n][r].data,
                        rowSpan: 'row_null',
                        property: data[n][r].property
                    } : data[n][r] = {
                        data: data[n][r].data,
                        rowSpan: 'row',
                        property: data[n][r].property};
                }
                else if (r == 0) {
                    var preNode = n > 0 ? data[n - 1][r].data : null;
                    (node == preNode) ? data[n][r] = {
                        data: data[n][r].data,
                        rowSpan: 'row_null',
                        property: data[n][r].property
                    } : data[n][r] = {
                        data: data[n][r].data,
                        rowSpan: 'row',
                        property: data[n][r].property};
                }
            }
        }
        for(var  n = 0; n < data.length; n++) {
            var rowContent = "
";
            for (var m = 0; m < chartConfig.keys.length; m++) {
                if (m > 0) {
                    if (data[n][m].rowSpan == 'row_null' && data[n][m - 1].rowSpan == 'row_null') {
                        rowContent += "

	";
                    } else {
                        rowContent += "
"+data[n][m].data+"
	";
                    }
                } else {
                    if (data[n][m].rowSpan == 'row_null') {
                        rowContent += "

	";
                    } else {
                        rowContent += "
" + data[n][m].data + "
	";
                    }
                }
            }
            for (var y = chartConfig.keys.length; y < data[n].length; y++) {
                rowContent += "
"+data[n][y].data+"
	";
            }
            html = html + rowContent + "
";
        }
        return html;
    },
    selectDataNum: function (data, num, chartConfig) {
        var _this = this;
        $('select.optionNum').on('change', function (e) {
            var pageDataNum = e.target.value;
            var dataPage =_this.paginationProcessData(data, num, pageDataNum);
            $('tbody.scrollContent').html(_this.render(dataPage[0], chartConfig));
            var liStr = '';
            dataPage.map(function(d, i){
                liStr += '
' + (i + 1) + '
';
            });
            $('.page>ul').html(liStr);
            _this.clickPageNum(dataPage, chartConfig);
        });
    },
    clickPageNum: function (data, chartConfig) {
        var _this = this;
        $('a.pageLink').on('click', function (e) {
            var pageNum = e.target.innerText - 1;

            $('tbody.scrollContent').html(_this.render(data[pageNum], chartConfig));
        });
    },
    export: function() {
        var idTmr;
        function  getExplorer() {
            var explorer = window.navigator.userAgent ;
            if (explorer.indexOf("MSIE") >= 0) {
                return 'ie';
            }
            else if (explorer.indexOf("Firefox") >= 0) {
                return 'Firefox';
            }
            else if(explorer.indexOf("Chrome") >= 0){
                return 'Chrome';
            }
            else if(explorer.indexOf("Opera") >= 0){
                return 'Opera';
            }
            else if(explorer.indexOf("Safari") >= 0){
                return 'Safari';
            }
        };

        function Cleanup() {
            window.clearInterval(idTmr);
            CollectGarbage();
        };

        var tableToExcel = (function() {
            var uri = 'data:application/vnd.ms-excel;base64,',
                template = '<meta charset="UTF-8">
{table}
',
                base64 = function(s) { return window.btoa(unescape(encodeURIComponent(s))) },
                format = function(s, c) {
                    return s.replace(/{(\w+)}/g,
                        function(m, p) { return c[p]; }) };
            return function(table, name) {
                if (!table.nodeType) table = document.getElementById(table);
                var ctx = {worksheet: name || 'Worksheet', table: table.innerHTML};
                window.location.href = uri + base64(format(template, ctx))
            }
        })();

        $(".exportBnt").on('click', function(){
            if(getExplorer()=='ie') {
                var curTbl = document.getElementById('tableWrapper');
                var oXL = new ActiveXObject("Excel.Application");
                var oWB = oXL.Workbooks.Add();
                var xlsheet = oWB.Worksheets(1);
                var sel = document.body.createTextRange();
                sel.moveToElementText(curTbl);
                sel.select();
                sel.execCommand("Copy");
                xlsheet.Paste();
                oXL.Visible = true;
                try {
                    var fname = oXL.Application.GetSaveAsFilename("Excel .xls", "Excel Spreadsheets (* .xls), * .xls");
                } catch (e) {
                    print("Nested catch caught " + e);
                } finally {
                    oWB.SaveAs(fname);
                    oWB.Close(savechanges = true);
                    oXL.Quit();
                    oXL = null;
                    idTmr = window.setInterval("Cleanup();", 1);
                }
            } else {
                tableToExcel('tableWrapper');
            }
        });
    }
};<commit_msg>Add paging for cross table #62, fix bug,<commit_after>/**
 * Created by Fine on 2016/12/4.
 */

var crossTable = {
    table: function(args){
        var data = args.data,
            chartConfig = args.chartConfig,
            tall = args.tall,
            pageDataNum = 20,
            container = args.container;
        var html = "

",
            colContent = "
";
        for (var i = 0; i < chartConfig.groups.length; i++) {
            var colspan = 1;
            var colList = [];
            for (var t = 0; t < chartConfig.keys.length; t++) {
                colContent += "

	";
            }
            for (var y = chartConfig.keys.length; y < data[i].length; y++) {
                if ((data[i][y + 1]) && (data[i][y].data == data[i][y + 1].data)) {
                    if(i > 0) {
                        var noEqual = false;
                        for (var s = i - 1; s > -1; i--) {
                            if (data[s][y].data != data[s][y + 1].data) {
                                noEqual = true;
                                break;
                            }
                        }
                        if (noEqual) {
                            colList.push({
                                data: data[i][y].data,
                                colSpan: colspan,
                                property: data[i][y].property
                            });
                            colspan = 1;
                        }
                        else {
                            colspan++;
                        }
                        // if (data[i - 1][y].data == data[i - 1][y + 1].data) {
                        //     colspan++
                        // }
                        // else {
                        //     colList.push({
                        //         data: data[i][y].data,
                        //         colSpan: colspan,
                        //         property: data[i][y].property
                        //     });
                        //     colspan = 1;
                        // }
                    }
                    else if(i == 0) {
                        colspan++;
                    }
                }
                else {
                    data[i][y] != data[i][y - 1] ? colList.push({
                        data: data[i][y].data,
                        colSpan: colspan,
                        property: data[i][y].property
                    }) : null;
                    colspan = 1;
                }
            }
            for (var c = 0; c < colList.length; c++) {
                colContent += colList[c].colSpan > 1 ? "
" + colList[c].data + "
	" :
                "
" + colList[c].data + "
	";
            }
            colContent += "

";
        }
        for (var k = 0; k < data[chartConfig.groups.length].length; k++) {
            colContent += "
" + data[chartConfig.groups.length][k].data + "
	";
        }
        html += colContent + "

";
        var dataPage = this.paginationProcessData(data, chartConfig.groups.length + 1, pageDataNum);
        var trDom = this.render(dataPage[0], chartConfig);
        html = html + trDom + "

";
        var PaginationDom = "

";
        var optionDom = "<select class='optionNum'><option value='20'>20</option><option value='50'>50</option><option value='100'>100</option><option value=''>150</option></select>";
        var operate = "
<button class='exportBnt'>export</button>" + optionDom + "
";

        $(container).html(operate);
        $(container).append("
" + html + "
");
        $(container).append(PaginationDom);
        dataPage.map(function(d, i){
            $('.page>ul').append('
' + (i + 1) + '
');
        });
        this.clickPageNum(dataPage, chartConfig);
        this.selectDataNum(data, chartConfig.groups.length + 1, chartConfig);
        this.export();
    },
    paginationProcessData: function (data, num, pageDataNum) {
        var length = data.length - num;
        var t = length % pageDataNum;
        var rest = parseInt(length / pageDataNum);
        var page;
        var pageData = [];
        t == 0 ? page = rest : page = rest + 1;
        for (var i = 1; i < page + 1; i++) {
            var partData = [];
            if (i == page) {
                for (var j = (i - 1) * pageDataNum + num; j < pageDataNum * (page - 1) + t + num; j++) {
                    partData.push(data[j]);
                }
            } else {
                for (var j = (i - 1) * pageDataNum + num; j < pageDataNum * i + num; j++) {
                    partData.push(data[j]);
                }
            }
            pageData.push(partData);
        }
        return pageData;
    },
    render: function (data, chartConfig) {
        var html = '';
        for (var r = 0; r < chartConfig.keys.length; r++) {
            for(var n = 1; n < data.length; n++){
                var node = data[n][r].data;
                if (r > 0) {
                    var parent = data[n][r - 1].data;
                    var next;
                    n > 0 ? next = data[n - 1][r - 1].data : null;
                    (node == data[n - 1][r].data && parent == next) ? data[n][r] = {
                        data: data[n][r].data,
                        rowSpan: 'row_null',
                        property: data[n][r].property
                    } : data[n][r] = {
                        data: data[n][r].data,
                        rowSpan: 'row',
                        property: data[n][r].property};
                }
                else if (r == 0) {
                    var preNode = n > 0 ? data[n - 1][r].data : null;
                    (node == preNode) ? data[n][r] = {
                        data: data[n][r].data,
                        rowSpan: 'row_null',
                        property: data[n][r].property
                    } : data[n][r] = {
                        data: data[n][r].data,
                        rowSpan: 'row',
                        property: data[n][r].property};
                }
            }
        }
        for(var n = 0; n < data.length; n++) {
            var rowContent = "
";
            var isFirstLine = (n == 0) ? true : false;
            for (var m = 0; m < chartConfig.keys.length; m++) {
                var currentCell = data[n][m];
                var rowParentCell = data[n][m-1];
                if (m > 0) {
                    if (currentCell.rowSpan == 'row_null' && rowParentCell.rowSpan == 'row_null' && !isFirstLine) {
                        rowContent += "

	";
                    } else {
                        rowContent += "
" + currentCell.data+"
	";
                    }
                } else {
                    if (currentCell.rowSpan == 'row_null' && !isFirstLine) {
                        rowContent += "

	";
                    } else {
                        rowContent += "
" + currentCell.data + "
	";
                    }
                }
            }
            for (var y = chartConfig.keys.length; y < data[n].length; y++) {
                rowContent += "
"+data[n][y].data+"
	";
            }
            html = html + rowContent + "
";
        }
        return html;
    },
    selectDataNum: function (data, num, chartConfig) {
        var _this = this;
        $('select.optionNum').on('change', function (e) {
            var pageDataNum = e.target.value;
            var dataPage =_this.paginationProcessData(data, num, pageDataNum);
            $('tbody.scrollContent').html(_this.render(dataPage[0], chartConfig));
            var liStr = '';
            dataPage.map(function(d, i){
                liStr += '
' + (i + 1) + '
';
            });
            $('.page>ul').html(liStr);
            _this.clickPageNum(dataPage, chartConfig);
        });
    },
    clickPageNum: function (data, chartConfig) {
        var _this = this;
        $('a.pageLink').on('click', function (e) {
            var pageNum = e.target.innerText - 1;

            $('tbody.scrollContent').html(_this.render(data[pageNum], chartConfig));
        });
    },
    export: function() {
        var idTmr;
        function  getExplorer() {
            var explorer = window.navigator.userAgent ;
            if (explorer.indexOf("MSIE") >= 0) {
                return 'ie';
            }
            else if (explorer.indexOf("Firefox") >= 0) {
                return 'Firefox';
            }
            else if(explorer.indexOf("Chrome") >= 0){
                return 'Chrome';
            }
            else if(explorer.indexOf("Opera") >= 0){
                return 'Opera';
            }
            else if(explorer.indexOf("Safari") >= 0){
                return 'Safari';
            }
        };

        function Cleanup() {
            window.clearInterval(idTmr);
            CollectGarbage();
        };

        var tableToExcel = (function() {
            var uri = 'data:application/vnd.ms-excel;base64,',
                template = '<meta charset="UTF-8">
{table}
',
                base64 = function(s) { return window.btoa(unescape(encodeURIComponent(s))) },
                format = function(s, c) {
                    return s.replace(/{(\w+)}/g,
                        function(m, p) { return c[p]; }) };
            return function(table, name) {
                if (!table.nodeType) table = document.getElementById(table);
                var ctx = {worksheet: name || 'Worksheet', table: table.innerHTML};
                window.location.href = uri + base64(format(template, ctx))
            }
        })();

        $(".exportBnt").on('click', function(){
            if(getExplorer()=='ie') {
                var curTbl = document.getElementById('tableWrapper');
                var oXL = new ActiveXObject("Excel.Application");
                var oWB = oXL.Workbooks.Add();
                var xlsheet = oWB.Worksheets(1);
                var sel = document.body.createTextRange();
                sel.moveToElementText(curTbl);
                sel.select();
                sel.execCommand("Copy");
                xlsheet.Paste();
                oXL.Visible = true;
                try {
                    var fname = oXL.Application.GetSaveAsFilename("Excel .xls", "Excel Spreadsheets (* .xls), * .xls");
                } catch (e) {
                    print("Nested catch caught " + e);
                } finally {
                    oWB.SaveAs(fname);
                    oWB.Close(savechanges = true);
                    oXL.Quit();
                    oXL = null;
                    idTmr = window.setInterval("Cleanup();", 1);
                }
            } else {
                tableToExcel('tableWrapper');
            }
        });
    }
};<|endoftext|>


<commit_before>import commander from 'commander';

import { configFile } from './DEFAULTS';
import Logger from './Logger';
import compareReportsCommand from './commands/compareReports';
import devCommand from './commands/dev';
import generateDevSha from './generateDevSha';
import hasReportCommand from './commands/hasReport';
import loadUserConfig from './loadUserConfig';
import packageJson from '../package.json';
import debugCommand from './commands/debug';
import runCommand from './commands/run';
import startJobCommand from './commands/startJob';
import postGithubComment from './postGithubComment';
import uploadReport from './uploadReport';

commander
  .version(packageJson.version)
  .option('-c, --config <path>', 'set config path', configFile)
  .option('-o, --only <component>', 'limit to one component')
  .option('-l, --link <url>', 'provide a link back to the commit')
  .option(
    '-m, --message <message>',
    'associate the run with a message (e.g. commit subject)',
  )
  .option('-a, --author <email>', 'the author of the commit')
  .option('--debug-port <port>', 'the port where the debug server listens')
  .option('--debug-port <port>', 'the port where the debug server listens')
  .option(
    '--dry-run',
    'makes the `happo compare` call non-destructive when running with a `compareThreshold` config option',
  )
  .usage('[options]');

commander
  .command('run [sha]')
  .description('execute a full happo run')
  .action(async (sha) => {
    let usedSha = sha || generateDevSha();
    if (!sha) {
      new Logger().info(
        `No [sha] provided. A temporary one will be used in place: "${usedSha}".`,
      );
    }
    if (commander.only) {
      usedSha = `${usedSha}-${commander.only}`;
    }
    await runCommand(usedSha, await loadUserConfig(commander.config), {
      only: commander.only,
      link: commander.link,
      message: commander.message,
    });
    process.exit(0);
  });

commander
  .command('dev')
  .description('start dev mode')
  .action(async () => {
    await devCommand(await loadUserConfig(commander.config), {
      only: commander.only,
    });
  });

commander
  .command('debug')
  .description('start a local server where you can debug happo examples')
  .action(async () => {
    debugCommand(
      { port: commander.debugPort },
      await loadUserConfig(commander.config),
    );
  });

commander
  .command('has-report <sha>')
  .description('check if there is a report for a specific sha')
  .action(async (sha) => {
    if (await hasReportCommand(sha, await loadUserConfig(commander.config))) {
      process.exit(0);
    } else {
      process.exit(1);
    }
  });

commander
  .command('empty <sha>')
  .description('mark a report as empty')
  .action(async (sha) => {
    await uploadReport(
      Object.assign({ snaps: [], sha }, await loadUserConfig(commander.config)),
    );
    process.exit(0);
  });

commander
  .command('compare <sha1> <sha2>')
  .description('compare reports for two different shas')
  .action(async (sha1, sha2) => {
    const config = await loadUserConfig(commander.config);
    const result = await compareReportsCommand(sha1, sha2, config, {
      link: commander.link,
      message: commander.message,
      author: commander.author,
      dryRun: commander.dryRun,
    });
    if (commander.link && process.env.HAPPO_GITHUB_USER_CREDENTIALS) {
      await postGithubComment({
        link: commander.link,
        statusImageUrl: result.statusImageUrl,
        compareUrl: result.compareUrl,
        githubApiUrl: config.githubApiUrl,
      });
    }
    new Logger().info(result.summary);
    if (result.equal) {
      process.exit(0);
    } else {
      process.exit(113);
    }
  });

commander
  .command('start-job <sha1> <sha2>')
  .description('start a job (used by happo-ci script)')
  .action(async (sha1, sha2) => {
    const result = await startJobCommand(
      sha1,
      sha2,
      {
        link: commander.link,
        message: commander.message,
      },
      await loadUserConfig(commander.config),
    );
    new Logger().info(result.id);
  });

export default function executeCli(argv) {
  if (!argv.slice(2).length) {
    commander.help();
    return;
  }
  return commander.parse(argv);
}
<commit_msg>feat: warn appropriately if an unknown cmd was passed in<commit_after>import commander from 'commander';

import { configFile } from './DEFAULTS';
import Logger from './Logger';
import compareReportsCommand from './commands/compareReports';
import devCommand from './commands/dev';
import generateDevSha from './generateDevSha';
import hasReportCommand from './commands/hasReport';
import loadUserConfig from './loadUserConfig';
import packageJson from '../package.json';
import debugCommand from './commands/debug';
import runCommand from './commands/run';
import startJobCommand from './commands/startJob';
import postGithubComment from './postGithubComment';
import uploadReport from './uploadReport';

commander
  .version(packageJson.version)
  .option('-c, --config <path>', 'set config path', configFile)
  .option('-o, --only <component>', 'limit to one component')
  .option('-l, --link <url>', 'provide a link back to the commit')
  .option(
    '-m, --message <message>',
    'associate the run with a message (e.g. commit subject)',
  )
  .option('-a, --author <email>', 'the author of the commit')
  .option('--debug-port <port>', 'the port where the debug server listens')
  .option('--debug-port <port>', 'the port where the debug server listens')
  .option(
    '--dry-run',
    'makes the `happo compare` call non-destructive when running with a `compareThreshold` config option',
  )
  .usage('[options]');

commander
  .command('run [sha]')
  .description('execute a full happo run')
  .action(async (sha) => {
    let usedSha = sha || generateDevSha();
    if (!sha) {
      new Logger().info(
        `No [sha] provided. A temporary one will be used in place: "${usedSha}".`,
      );
    }
    if (commander.only) {
      usedSha = `${usedSha}-${commander.only}`;
    }
    await runCommand(usedSha, await loadUserConfig(commander.config), {
      only: commander.only,
      link: commander.link,
      message: commander.message,
    });
    process.exit(0);
  });

commander
  .command('dev')
  .description('start dev mode')
  .action(async () => {
    await devCommand(await loadUserConfig(commander.config), {
      only: commander.only,
    });
  });

commander
  .command('debug')
  .description('start a local server where you can debug happo examples')
  .action(async () => {
    debugCommand(
      { port: commander.debugPort },
      await loadUserConfig(commander.config),
    );
  });

commander
  .command('has-report <sha>')
  .description('check if there is a report for a specific sha')
  .action(async (sha) => {
    if (await hasReportCommand(sha, await loadUserConfig(commander.config))) {
      process.exit(0);
    } else {
      process.exit(1);
    }
  });

commander
  .command('empty <sha>')
  .description('mark a report as empty')
  .action(async (sha) => {
    await uploadReport(
      Object.assign({ snaps: [], sha }, await loadUserConfig(commander.config)),
    );
    process.exit(0);
  });

commander
  .command('compare <sha1> <sha2>')
  .description('compare reports for two different shas')
  .action(async (sha1, sha2) => {
    const config = await loadUserConfig(commander.config);
    const result = await compareReportsCommand(sha1, sha2, config, {
      link: commander.link,
      message: commander.message,
      author: commander.author,
      dryRun: commander.dryRun,
    });
    if (commander.link && process.env.HAPPO_GITHUB_USER_CREDENTIALS) {
      await postGithubComment({
        link: commander.link,
        statusImageUrl: result.statusImageUrl,
        compareUrl: result.compareUrl,
        githubApiUrl: config.githubApiUrl,
      });
    }
    new Logger().info(result.summary);
    if (result.equal) {
      process.exit(0);
    } else {
      process.exit(113);
    }
  });

commander
  .command('start-job <sha1> <sha2>')
  .description('start a job (used by happo-ci script)')
  .action(async (sha1, sha2) => {
    const result = await startJobCommand(
      sha1,
      sha2,
      {
        link: commander.link,
        message: commander.message,
      },
      await loadUserConfig(commander.config),
    );
    new Logger().info(result.id);
  });

commander.on('command:*', () => {
  console.error('Invalid command: %s\nSee --help for a list of available commands.', commander.args.join(' '));
  process.exit(1);
});

export default function executeCli(argv) {
  if (!argv.slice(2).length) {
    commander.help();
    return;
  }
  return commander.parse(argv);
}
<|endoftext|>


<commit_before>var CKAN = {};

var isNodeModule = (typeof module !== 'undefined' && module != null && typeof require !== 'undefined');

if (isNodeModule) {
  var _ = require('underscore')
    , request = require('request')
    ;
  module.exports = CKAN;
}

(function(my) {
  my.Client = function(endpoint, apiKey) {
    this.endpoint = _getEndpoint(endpoint);
    this.apiKey = apiKey;
  };

  my.Client.prototype.action = function(name, data, cb) {
    if (name.indexOf('dataset_' === 0)) {
      name = name.replace('dataset_', 'package_');
    }
    var options = {
      url: this.endpoint + '/3/action/' + name,
      data: data,
      type: 'POST'
    };
    return this._ajax(options, cb);
  };

  // make an AJAX request
  my.Client.prototype._ajax = function(options, cb) {
    options.headers = options.headers || {};
    if (this.apiKey) {
      options.headers['X-CKAN-API-KEY'] = this.apiKey;
    }
    var meth = isNodeModule ? _nodeRequest : _browserRequest;
    return meth(options, cb);
  }

  // Like search but supports ReclineJS style query structure
  //
  // Primarily for use by Recline backend below
  my.Client.prototype.datastoreQuery = function(queryObj, cb) {
    var actualQuery = my._normalizeQuery(queryObj);
    this.action('datastore_search', actualQuery, function(err, results) {
      if (err) {
        cb(err);
        return;
      }

      // map ckan types to our usual types ...
      var fields = _.map(results.result.fields, function(field) {
        field.type = field.type in my.ckan2JsonTableSchemaTypes ? my.ckan2JsonTableSchemaTypes[field.type] : field.type;
        return field;
      });
      var out = {
        total: results.result.total,
        fields: fields,
        hits: results.result.records
      };
      cb(null, out);
    });
  };

  my.Client.prototype.datastoreSqlQuery = function(sql, cb) {
    this.action('datastore_search_sql', {sql: sql}, function(err, results) {
      if (err) {
        var parsed = JSON.parse(err.message);
        var errOut = {
          original: err,
          code: err.code,
          message: parsed.error.info.orig[0]
        };
        cb(errOut);
        return;
      }

      // map ckan types to our usual types ...
      var fields = _.map(results.result.fields, function(field) {
        field.type = field.type in my.ckan2JsonTableSchemaTypes ? my.ckan2JsonTableSchemaTypes[field.type] : field.type;
        return field;
      });
      var out = {
        total: results.result.length,
        fields: fields,
        hits: results.result.records
      };
      cb(null, out);
    });
  };

  my.ckan2JsonTableSchemaTypes = {
    'text': 'string',
    'int': 'integer',
    'int4': 'integer',
    'int8': 'integer',
    'float8': 'float',
    'timestamp': 'datetime',
    'bool': 'boolean',
  };

  //
  my.jsonTableSchema2CkanTypes = {
    'string': 'text',
    'number': 'float',
    'integer': 'int',
    'datetime': 'timestamp',
    'boolean': 'bool',
    'binary': 'bytea',
    'object': 'json',
    'array': 'text[]',
    'any': 'text'
  };

  // list all the resources with an entry in the DataStore
  my.Client.prototype.datastoreResources = function(cb) {
    var data = {
      resource_id: '_table_metadata'
    };
    return this.action('datastore_search', data, cb);
  };

  // Utilities
  // =========

  var _getEndpoint = function(endpoint) {
    endpoint = endpoint || '/';
    // strip trailing /
    endpoint = endpoint.replace(/\/$/, '');
    if (!endpoint.match(/\/api$/)) {
      endpoint += '/api';
    }
    return endpoint;
  };

  var _nodeRequest = function(options, cb) {
    var conf = {
      url: options.url,
      headers: options.headers || {},
      method: options.type || 'GET',
      json: options.data
    };
    // we could just call request but that's a PITA to mock plus request.get = request (if you look at the source code)
    request(conf, function(err, res, body) {
      if (!err && res && !(res.statusCode === 200 || res.statusCode === 302)) {
        err = 'CKANJS API Error. HTTP code ' + res.statusCode + '. Message: ' + JSON.stringify(body, null, 2);
      }
      cb(err, body);
    });
  };

  var _browserRequest = function(options, cb) {
    var self = this;
    options.data = encodeURIComponent(JSON.stringify(options.data));
    options.success = function(data) {
      cb(null, data);
    }
    options.error = function(obj, obj2, obj3) {
      var err = {
        code: obj.status,
        message: obj.responseText
      }
      cb(err);
    }
    if (options.headers) {
      options.beforeSend = function(req) {
        for (key in options.headers) {
          req.setRequestHeader(key, options.headers[key]);
        }
      };
    }
    return jQuery.ajax(options);
  };

  // only put in the module namespace so we can access for tests!
  my._normalizeQuery = function(queryObj) {
    var actualQuery = {
      resource_id: queryObj.resource_id,
      q: queryObj.q,
      filters: {},
      limit: queryObj.size || 10,
      offset: queryObj.from || 0
    };

    if (queryObj.sort && queryObj.sort.length > 0) {
      var _tmp = _.map(queryObj.sort, function(sortObj) {
        return sortObj.field + ' ' + (sortObj.order || '');
      });
      actualQuery.sort = _tmp.join(',');
    }

    if (queryObj.filters && queryObj.filters.length > 0) {
      _.each(queryObj.filters, function(filter) {
        if (filter.type === "term") {
          actualQuery.filters[filter.field] = filter.term;
        }
      });
    }
    return actualQuery;
  };

  // Parse a normal CKAN resource URL and return API endpoint etc
  //
  // Normal URL is something like http://demo.ckan.org/dataset/some-dataset/resource/eb23e809-ccbb-4ad1-820a-19586fc4bebd
  //
  // :return: { resource_id: ..., endpoint: ... }
  my.parseCkanResourceUrl = function(url) {
    parts = url.split('/');
    var len = parts.length;
    return {
      resource_id: parts[len-1],
      endpoint: parts.slice(0,[len-4]).join('/') + '/api'
    };
  };
}(CKAN));

// Recline Wrapper
//
// Wrap the DataStore to create a Backend suitable for usage in ReclineJS
//
// This provides connection to the CKAN DataStore (v2)
//
// General notes
//
// We need 2 things to make most requests:
//
// 1. CKAN API endpoint
// 2. ID of resource for which request is being made
//
// There are 2 ways to specify this information.
//
// EITHER (checked in order):
//
// * Every dataset must have an id equal to its resource id on the CKAN instance
// * The dataset has an endpoint attribute pointing to the CKAN API endpoint
//
// OR:
//
// Set the url attribute of the dataset to point to the Resource on the CKAN instance. The endpoint and id will then be automatically computed.
var recline = recline || {};
recline.Backend = recline.Backend || {};
recline.Backend.Ckan = recline.Backend.Ckan || {};
(function(my) {
  my.__type__ = 'ckan';

  // private - use either jQuery or Underscore Deferred depending on what is available
  var Deferred = jQuery.Deferred;

  // ### fetch
  my.fetch = function(dataset) {
    var dfd = new Deferred()
    my.query({}, dataset)
      .done(function(data) {
        dfd.resolve({
          fields: data.fields,
          records: data.hits
        });
      })
      .fail(function(err) {
        dfd.reject(err);
      })
      ;
    return dfd.promise();
  };

  my.query = function(queryObj, dataset) {
    var dfd = new Deferred()
      , wrapper
      ;
    if (dataset.endpoint) {
      wrapper = new CKAN.Client(dataset.endpoint);
    } else {
      var out = CKAN.parseCkanResourceUrl(dataset.url);
      dataset.id = out.resource_id;
      wrapper = new CKAN.Client(out.endpoint);
    }
    queryObj.resource_id = dataset.id;
    wrapper.datastoreQuery(queryObj, function(err, out) {
      if (err) {
        dfd.reject(err);
      } else {
        dfd.resolve(out);
      }
    });
    return dfd.promise();
  };
}(recline.Backend.Ckan));
<commit_msg>fix bug in ckan.js<commit_after>var CKAN = {};

var isNodeModule = (typeof module !== 'undefined' && module != null && typeof require !== 'undefined');

if (isNodeModule) {
  var _ = require('underscore')
    , request = require('request')
    ;
  module.exports = CKAN;
} else{
    var _ = jQuery;
}

(function(my) {
  my.Client = function(endpoint, apiKey) {
    this.endpoint = _getEndpoint(endpoint);
    this.apiKey = apiKey;
  };

  my.Client.prototype.action = function(name, data, cb) {
    if (name.indexOf('dataset_' === 0)) {
      name = name.replace('dataset_', 'package_');
    }
    var options = {
      url: this.endpoint + '/3/action/' + name,
      data: data,
      type: 'POST'
    };
    return this._ajax(options, cb);
  };

  // make an AJAX request
  my.Client.prototype._ajax = function(options, cb) {
    options.headers = options.headers || {};
    if (this.apiKey) {
      options.headers['X-CKAN-API-KEY'] = this.apiKey;
    }
    var meth = isNodeModule ? _nodeRequest : _browserRequest;
    return meth(options, cb);
  }

  // Like search but supports ReclineJS style query structure
  //
  // Primarily for use by Recline backend below
  my.Client.prototype.datastoreQuery = function(queryObj, cb) {
    var actualQuery = my._normalizeQuery(queryObj);
    this.action('datastore_search', actualQuery, function(err, results) {
      if (err) {
        cb(err);
        return;
      }

      // map ckan types to our usual types ...
      var fields = _.map(results.result.fields, function(field) {
        field.type = field.type in my.ckan2JsonTableSchemaTypes ? my.ckan2JsonTableSchemaTypes[field.type] : field.type;
        return field;
      });
      var out = {
        total: results.result.total,
        fields: fields,
        hits: results.result.records
      };
      cb(null, out);
    });
  };

  my.Client.prototype.datastoreSqlQuery = function(sql, cb) {
    this.action('datastore_search_sql', {sql: sql}, function(err, results) {
      if (err) {
        var parsed = JSON.parse(err.message);
        var errOut = {
          original: err,
          code: err.code,
          message: parsed.error.info.orig[0]
        };
        cb(errOut);
        return;
      }

      // map ckan types to our usual types ...
      var fields = _.map(results.result.fields, function(field) {
        field.type = field.type in my.ckan2JsonTableSchemaTypes ? my.ckan2JsonTableSchemaTypes[field.type] : field.type;
        return field;
      });
      var out = {
        total: results.result.length,
        fields: fields,
        hits: results.result.records
      };
      cb(null, out);
    });
  };

  my.ckan2JsonTableSchemaTypes = {
    'text': 'string',
    'int': 'integer',
    'int4': 'integer',
    'int8': 'integer',
    'float8': 'float',
    'timestamp': 'datetime',
    'bool': 'boolean',
  };

  //
  my.jsonTableSchema2CkanTypes = {
    'string': 'text',
    'number': 'float',
    'integer': 'int',
    'datetime': 'timestamp',
    'boolean': 'bool',
    'binary': 'bytea',
    'object': 'json',
    'array': 'text[]',
    'any': 'text'
  };

  // list all the resources with an entry in the DataStore
  my.Client.prototype.datastoreResources = function(cb) {
    var data = {
      resource_id: '_table_metadata'
    };
    return this.action('datastore_search', data, cb);
  };

  // Utilities
  // =========

  var _getEndpoint = function(endpoint) {
    endpoint = endpoint || '/';
    // strip trailing /
    endpoint = endpoint.replace(/\/$/, '');
    if (!endpoint.match(/\/api$/)) {
      endpoint += '/api';
    }
    return endpoint;
  };

  var _nodeRequest = function(options, cb) {
    var conf = {
      url: options.url,
      headers: options.headers || {},
      method: options.type || 'GET',
      json: options.data
    };
    // we could just call request but that's a PITA to mock plus request.get = request (if you look at the source code)
    request(conf, function(err, res, body) {
      if (!err && res && !(res.statusCode === 200 || res.statusCode === 302)) {
        err = 'CKANJS API Error. HTTP code ' + res.statusCode + '. Message: ' + JSON.stringify(body, null, 2);
      }
      cb(err, body);
    });
  };

  var _browserRequest = function(options, cb) {
    var self = this;
    options.data = encodeURIComponent(JSON.stringify(options.data));
    options.success = function(data) {
      cb(null, data);
    }
    options.error = function(obj, obj2, obj3) {
      var err = {
        code: obj.status,
        message: obj.responseText
      }
      cb(err);
    }
    if (options.headers) {
      options.beforeSend = function(req) {
        for (key in options.headers) {
          req.setRequestHeader(key, options.headers[key]);
        }
      };
    }
    return jQuery.ajax(options);
  };

  // only put in the module namespace so we can access for tests!
  my._normalizeQuery = function(queryObj) {
    var actualQuery = {
      resource_id: queryObj.resource_id,
      q: queryObj.q,
      filters: {},
      limit: queryObj.size || 10,
      offset: queryObj.from || 0
    };

    if (queryObj.sort && queryObj.sort.length > 0) {
      var _tmp = _.map(queryObj.sort, function(sortObj) {
        return sortObj.field + ' ' + (sortObj.order || '');
      });
      actualQuery.sort = _tmp.join(',');
    }

    if (queryObj.filters && queryObj.filters.length > 0) {
      _.each(queryObj.filters, function(filter) {
        if (filter.type === "term") {
          actualQuery.filters[filter.field] = filter.term;
        }
      });
    }
    return actualQuery;
  };

  // Parse a normal CKAN resource URL and return API endpoint etc
  //
  // Normal URL is something like http://demo.ckan.org/dataset/some-dataset/resource/eb23e809-ccbb-4ad1-820a-19586fc4bebd
  //
  // :return: { resource_id: ..., endpoint: ... }
  my.parseCkanResourceUrl = function(url) {
    parts = url.split('/');
    var len = parts.length;
    return {
      resource_id: parts[len-1],
      endpoint: parts.slice(0,[len-4]).join('/') + '/api'
    };
  };
}(CKAN));

// Recline Wrapper
//
// Wrap the DataStore to create a Backend suitable for usage in ReclineJS
//
// This provides connection to the CKAN DataStore (v2)
//
// General notes
//
// We need 2 things to make most requests:
//
// 1. CKAN API endpoint
// 2. ID of resource for which request is being made
//
// There are 2 ways to specify this information.
//
// EITHER (checked in order):
//
// * Every dataset must have an id equal to its resource id on the CKAN instance
// * The dataset has an endpoint attribute pointing to the CKAN API endpoint
//
// OR:
//
// Set the url attribute of the dataset to point to the Resource on the CKAN instance. The endpoint and id will then be automatically computed.
var recline = recline || {};
recline.Backend = recline.Backend || {};
recline.Backend.Ckan = recline.Backend.Ckan || {};
(function(my) {
  my.__type__ = 'ckan';

  // private - use either jQuery or Underscore Deferred depending on what is available
  var Deferred = jQuery.Deferred;

  // ### fetch
  my.fetch = function(dataset) {
    var dfd = new Deferred()
    my.query({}, dataset)
      .done(function(data) {
        dfd.resolve({
          fields: data.fields,
          records: data.hits
        });
      })
      .fail(function(err) {
        dfd.reject(err);
      })
      ;
    return dfd.promise();
  };

  my.query = function(queryObj, dataset) {
    var dfd = new Deferred()
      , wrapper
      ;
    if (dataset.endpoint) {
      wrapper = new CKAN.Client(dataset.endpoint);
    } else {
      var out = CKAN.parseCkanResourceUrl(dataset.url);
      dataset.id = out.resource_id;
      wrapper = new CKAN.Client(out.endpoint);
    }
    queryObj.resource_id = dataset.id;
    wrapper.datastoreQuery(queryObj, function(err, out) {
      if (err) {
        dfd.reject(err);
      } else {
        dfd.resolve(out);
      }
    });
    return dfd.promise();
  };
}(recline.Backend.Ckan));
<|endoftext|>


<commit_before>var _ = require("lodash");

module.exports = {
    get: function(ctx, id, callback){
        if(id === "eci"){
            callback(null, _.get(ctx,["event","eci"],_.get(ctx,["query","eci"])));
            return;
        }else if(id === "rulesetURI"){
            ctx.db.getEnabledRuleset(ctx.rid, function(err, data){
                if(err) return callback(err);
                callback(null, data.url);
            });
            return;
        }
        callback(new Error("Meta attribute not defined `" + id + "`"));
    }
};
<commit_msg>meta:rid<commit_after>var _ = require("lodash");

module.exports = {
    get: function(ctx, id, callback){
        if(id === "eci"){
            callback(null, _.get(ctx,["event","eci"],_.get(ctx,["query","eci"])));
            return;
        }else if(id === "rid"){
            callback(null, ctx.rid);
            return;
        }else if(id === "rulesetURI"){
            ctx.db.getEnabledRuleset(ctx.rid, function(err, data){
                if(err) return callback(err);
                callback(null, data.url);
            });
            return;
        }
        callback(new Error("Meta attribute not defined `" + id + "`"));
    }
};
<|endoftext|>


<commit_before>const Logger = require('../util/Logger.js')('YouTube');

const YTDL = require('youtube-dl');
const updater = require('youtube-dl/lib/downloader');
const ytdl = require('ytdl-core');

const urlRegex = require('url-regex');
const youtubeRegex = require('youtube-regex');

const FS = require('fs');
const Stream = require('stream');

module.exports = {};

module.exports.update = () => {
	Logger.log('Updating youtube-dl binaries.');

	updater('./node_modules/youtube-dl/bin/', (err, done) => {
		if (err) return Logger.error(err);

		Logger.log(done);
	});
};

module.exports.fetchInfo = (url, format = 'm4a') => {
	if (!urlRegex().test(url)) return Promise.reject(Logger.format('Not a valid url'));

	return new Promise((resolve, reject) => {
		Logger.log('Fetching track information');

		YTDL.getInfo(url, ['-o', '/%(title)s - %(id)s.%(ext)s'],
				{ maxBuffer: 10 * 1000 * 1024 }, (err, info) => {

			if (err) return reject(Logger.format(err));

			let infoEntries = [];

			if (!Array.isArray(info)) {
				infoEntries.push(info);
				// Temporary fix for bug in ytdl api
				infoEntries[0].url = url;
			} else {
				infoEntries = info;
			}

			let result = infoEntries.map(info => {
				let filename = info._filename
						.replace(/^(.+\.).+$/, `$1${format}`);

				let filenameFormat = `^(.+) - (.+)(\\.${format})$`;

				return {
					id: filename.match(filenameFormat)[2],
					title: filename.match(filenameFormat)[1],
					filename: filename,
					url: info.url
				};
			});

			if (result.length > 1) {
				resolve({
					playlistId: infoEntries[0].playlist_id,
					playlistTitle: infoEntries[0].playlist_title,
					playlist: infoEntries[0].playlist_title,
					entries: result
				});
			} else {
				resolve({
					playlist: null,
					entries: result
				});
			}
		});
	});
};

/**
 * Only supports links directly from YouTube, can't stream from elsewhere.
 */
module.exports.stream = (url, quality = 140) => {
	if (!youtubeRegex().test(url)) return Logger.error('Not a valid url');

	Logger.log('Fetching audio stream');

	// To figure out quality use -F
	// Regex: https://regex101.com/r/C7umvu/1
	return ytdl(url, { filter: 'audioonly', quality: quality });
};

/**
 * Only supports links directly from YouTube, can't stream from elsewhere.
 */
module.exports.streamAndDownload = (filename, url, forceDownload = false, format = 'm4a', quality = 140, dir = 'media/youtube/') => {
	if (!youtubeRegex().test(url)) return Promise.reject(Logger.format('Not a valid url'));

	return new Promise((resolve, reject) => {
		if (!FS.existsSync(dir)) {
			FS.mkdirSync(dir);
		}

		if (forceDownload) {
			Logger.log('Audio cache disabled manually. Forcing (re)download');

		} else {
			Logger.log('Searching for cached version of audio track');

			let files = FS.readdirSync(dir);

			if (files.includes(filename)) {
				Logger.log('Cached audio track found. Skipping download');

				return resolve({ path: dir + filename });
			}

			Logger.log('Audio file not found in cache');
		}

		let stream = module.exports.stream(url);

		let fileStream = FS.createWriteStream(dir + filename);
		fileStream.on('finish', () => Logger.log('Audio track downloaded'));
		fileStream.on('error', Logger.error);

		let playbackBuffer = new Stream.PassThrough();

		stream.on('data', (data) => {
			fileStream.write(data);
			playbackBuffer.write(data);
		});
		stream.on('finish', () => {
			fileStream.end();
			playbackBuffer.end();
		});

		resolve({
			path: dir + filename,
			stream: playbackBuffer
		});
	});
};

module.exports.download = (filename, url, forceDownload = false, format = 'm4a', dir = 'media/youtube/') => {
	if (!urlRegex().test(url)) return Promise.reject(Logger.format('Not a valid url'));

	return new Promise((resolve, reject) => {
		if (!FS.existsSync(dir)) {
			FS.mkdirSync(dir);
		}

		if (forceDownload) {
			Logger.log('Audio cache disabled manually. Forcing (re)download');

		} else {
			Logger.log('Searching for cached version of audio track');

			let files = FS.readdirSync(dir);

			if (files.includes(filename)) {
				Logger.log('Cached audio track found. Skipping download');

				return resolve(dir + filename);
			}

			Logger.log('Audio file not found in cache');
		}

		Logger.log('Starting download of audio file');

		YTDL.exec(url, ['-x',
				'--audio-format', format,
				'-o', dir + '%(title)s - %(id)s.%(ext)s'],
				{}, (err, output) => {

			if (err) return reject(Logger.format(err));

			Logger.log(output.join('\n'));
			Logger.log('Audio track downloaded');

			resolve(dir + filename);
		});
	});
};
<commit_msg>Fixed playlists by using correct url attribute<commit_after>const Logger = require('../util/Logger.js')('YouTube');

const YTDL = require('youtube-dl');
const updater = require('youtube-dl/lib/downloader');
const ytdl = require('ytdl-core');

const urlRegex = require('url-regex');
const youtubeRegex = require('youtube-regex');

const FS = require('fs');
const Stream = require('stream');

module.exports = {};

module.exports.update = () => {
	Logger.log('Updating youtube-dl binaries.');

	updater('./node_modules/youtube-dl/bin/', (err, done) => {
		if (err) return Logger.error(err);

		Logger.log(done);
	});
};

module.exports.fetchInfo = (url, format = 'm4a') => {
	if (!urlRegex().test(url)) return Promise.reject(Logger.format('Not a valid url'));

	return new Promise((resolve, reject) => {
		Logger.log('Fetching track information');

		YTDL.getInfo(url, ['-o', '/%(title)s - %(id)s.%(ext)s'],
				{ maxBuffer: 10 * 1000 * 1024 }, (err, info) => {

			if (err) return reject(Logger.format(err));

			let infoEntries = [];

			if (!Array.isArray(info)) {
				infoEntries.push(info);
			} else {
				infoEntries = info;
			}

			let result = infoEntries.map(info => {
				let filename = info._filename
						.replace(/^(.+\.).+$/, `$1${format}`);

				let filenameFormat = `^(.+) - (.+)(\\.${format})$`;

				return {
					id: filename.match(filenameFormat)[2],
					title: filename.match(filenameFormat)[1],
					filename: filename,
					url: info.webpage_url
				};
			});

			if (result.length > 1) {
				resolve({
					playlistId: infoEntries[0].playlist_id,
					playlistTitle: infoEntries[0].playlist_title,
					playlist: infoEntries[0].playlist_title,
					entries: result
				});
			} else {
				resolve({
					playlist: null,
					entries: result
				});
			}
		});
	});
};

/**
 * Only supports links directly from YouTube, can't stream from elsewhere.
 */
module.exports.stream = (url, quality = 140) => {
	if (!youtubeRegex().test(url)) return Logger.error('Not a valid url');

	Logger.log('Fetching audio stream');

	// To figure out quality use -F
	// Regex: https://regex101.com/r/C7umvu/1
	return ytdl(url, { filter: 'audioonly', quality: quality });
};

/**
 * Only supports links directly from YouTube, can't stream from elsewhere.
 */
module.exports.streamAndDownload = (filename, url, forceDownload = false, format = 'm4a', quality = 140, dir = 'media/youtube/') => {
	if (!youtubeRegex().test(url)) return Promise.reject(Logger.format('Not a valid url'));

	return new Promise((resolve, reject) => {
		if (!FS.existsSync(dir)) {
			FS.mkdirSync(dir);
		}

		if (forceDownload) {
			Logger.log('Audio cache disabled manually. Forcing (re)download');

		} else {
			Logger.log('Searching for cached version of audio track');

			let files = FS.readdirSync(dir);

			if (files.includes(filename)) {
				Logger.log('Cached audio track found. Skipping download');

				return resolve({ path: dir + filename });
			}

			Logger.log('Audio file not found in cache');
		}

		let stream = module.exports.stream(url);

		let fileStream = FS.createWriteStream(dir + filename);
		fileStream.on('finish', () => Logger.log('Audio track downloaded'));
		fileStream.on('error', Logger.error);

		let playbackBuffer = new Stream.PassThrough();

		stream.on('data', (data) => {
			fileStream.write(data);
			playbackBuffer.write(data);
		});
		stream.on('finish', () => {
			fileStream.end();
			playbackBuffer.end();
		});

		resolve({
			path: dir + filename,
			stream: playbackBuffer
		});
	});
};

module.exports.download = (filename, url, forceDownload = false, format = 'm4a', dir = 'media/youtube/') => {
	if (!urlRegex().test(url)) return Promise.reject(Logger.format('Not a valid url'));

	return new Promise((resolve, reject) => {
		if (!FS.existsSync(dir)) {
			FS.mkdirSync(dir);
		}

		if (forceDownload) {
			Logger.log('Audio cache disabled manually. Forcing (re)download');

		} else {
			Logger.log('Searching for cached version of audio track');

			let files = FS.readdirSync(dir);

			if (files.includes(filename)) {
				Logger.log('Cached audio track found. Skipping download');

				return resolve(dir + filename);
			}

			Logger.log('Audio file not found in cache');
		}

		Logger.log('Starting download of audio file');

		YTDL.exec(url, ['-x',
				'--audio-format', format,
				'-o', dir + '%(title)s - %(id)s.%(ext)s'],
				{}, (err, output) => {

			if (err) return reject(Logger.format(err));

			Logger.log(output.join('\n'));
			Logger.log('Audio track downloaded');

			resolve(dir + filename);
		});
	});
};
<|endoftext|>