<commit_before>/**
 * Usage:
 * - 图片功能
 * Author:
 * - hopesfish at 163.com
 */
var ejs = require('ejs');
var conf = require('../../conf');
var wxconst = require("../const");
var utils = require("../utils");
var BaseServices = require("../../services/BaseServices");
var GalleryServices = require("../../services/GalleryServices");

module.exports = function(webot) {
    function add_gallery_start(info, next) {
        var user = info.session.parent || info.session.teacher;
        if (!user) {
            return next(null, "抱歉，您不是认证用户！");
        }

        info.wait("gallery type text");
        return next(null, ejs.render(
            ["分享孩子成长点滴到班级圈，仅供本班所有家长及老师访问。",
             '<a href="<%- url%>">点击这里使用网页版分享。',
             "使用微信对话框分享，请点击左下侧键盘图标后输入文字"].join("\n\n"),
            {
                url: conf.site_root + '/webot/wap/index.html?' +
                    BaseServices.getAuthoriedParams(info.session.school.id, user.id)
            }
        ));
    }
    // 等待主题输入
    webot.waitRule('gallery type text', function(info, next) {
        if (info.is("event")) {
            return next();
        }
        if (!info.is("text")) {
            utils.operation_is_failed(info, next);
            info.rewait("gallery type text");
            return next(null, "抱歉，只能输入文字。");
        }
        // 构造image
        info.session.gallery = {title: '', photos: []};
        info.session.gallery.title = info.text;

        info.wait("gallery type image or confirm");
        return next(null, "如何发布文字记录：\n回复【" + wxconst.YES + "】提交\n回复【" + wxconst.NO + "】取消\n\n如需发布图文记录，请上传图片。");
    });

    webot.waitRule('gallery type image or confirm', function(info, next) {
        if (info.is("event")) {
            delete info.session.gallery;
            return next();
        }

        var user = info.session.parent || info.session.teacher;

        // 接受提交指令
        if (info.is("text")) {
            if (info.text === wxconst.YES) {
                // 图片入库
                var time = (new Date()).getTime(), map = {}, photos = [];
                for (var i=0; i<info.session.gallery.photos.length; i++) {
                    var filename = '/school/' +  info.session.school.id + '/user/' + user.id + '/photo/' + (new Date()).getTime() + '/' + i;

                    if (map[info.session.gallery.photos[i]]) { continue; }

                    map[info.session.gallery.photos[i]] = filename;
                    photos.push(filename);
                    utils.download_image(info.session.gallery.photos[i], filename);
                }
                info.session.gallery.photos = photos;
                // 创建者
                info.session.gallery.createdBy = user.id;

                var derfer = '', classId = '';
                if (info.session.parent) {
                    derfer = GalleryServices.createAsParent(info.session.school.id, info.session.parent.students[0].id, info.session.gallery);
                    classId = info.session.parent.students[0].classId;
                } else if (info.session.teacher) {
                    derfer = GalleryServices.createAsTeacher(info.session.school.id, info.session.teacher.wexClasses[0].id, info.session.gallery);
                    classId = info.session.teacher.wexClasses[0].id;
                }

                derfer.then(function() {
                    delete info.session.gallery;

                    return next(null, ejs.render(
                        '成功提交!\n<a href="<%- url%>">点击这里查看。',
                        {
                            url: conf.site_root + '/webot/wap/index.html?' +
                                    BaseServices.getAuthoriedParams(info.session.school.id, user.id) +
                                    '#/class/' + classId + '/gallery'
                        }
                    ));
                }, function(err) {
                    delete info.session.gallery;
                    next(null, "抱歉，服务器异常，无法提交。");
                });
            } else if (info.text === wxconst.NO) {
                delete info.session.gallery;
                return next(null, "操作已取消。");
            } else {
                utils.operation_is_failed(info, next);
                info.rewait("gallery type image or confirm");
                return next(null, "只能回复【" + wxconst.YES + "】或者【" + wxconst.NO + "】，或者上传图片。");
            }
        } else if (info.is("image")) {
            // 构造image
            if (info.session.gallery) {
                info.session.gallery.photos.push(info.param.picUrl);
            }
            info.rewait("gallery type image or confirm");
            var len = info.session.gallery.photos.length;
            return next(null, "已存草稿图片" + len + "张，您可继续上传图片。\n\n回复【" + wxconst.YES + "】发布\n回复【" + wxconst.NO + "】取消");
        } else {
            utils.operation_is_failed(info, next);
            info.rewait("gallery type image or confirm");
            return next(null, "只能回复【" + wxconst.YES + "】或者【" + wxconst.NO + "】，或者上传图片。");
        }
    });

    function view_gallery(info, next) {
        var user = info.session.parent || info.session.teacher;
        if (!user) {
            return next(null, "抱歉，您不是认证用户！");
        }
        var classId = '';
        if (info.session.parent) {
            classId = info.session.parent.students[0].classId;
        } else if (info.session.teacher) {
            classId = info.session.teacher.wexClasses[0].id;
        }
        return next(null, ejs.render(
            '请<a href="<%- url%>">点击这里查看',
            {
                url: conf.site_root + '/webot/wap/index.html?' +
                        BaseServices.getAuthoriedParams(info.session.school.id, user.id) +
                        '#/class/' + classId + '/gallery'
            }
        ));
    }

    // 发布图片提示语
    webot.set('user gallery add start by text', {
        domain: "gateway",
        pattern: /^发布班级圈记录/i,
        handler: add_gallery_start
    });
    webot.set('user gallery add start by event', {
        domain: "gateway",
        pattern: function(info) {
            return info.param.eventKey === 'IMAGE_ADD';
        },
        handler: add_gallery_start
    });

    // 查看图片
    webot.set('user gallery view start by text', {
        domain: "gateway",
        pattern: /^(班级圈|我的班级圈)/i,
        handler: view_gallery
    });
    webot.set('user gallery view start by event', {
        domain: "gateway",
        pattern: function(info) {
            return info.param.eventKey === 'IMAGE_VIEW';
        },
        handler: view_gallery
    });
}<commit_msg>fix typo<commit_after>/**
 * Usage:
 * - 图片功能
 * Author:
 * - hopesfish at 163.com
 */
var ejs = require('ejs');
var conf = require('../../conf');
var wxconst = require("../const");
var utils = require("../utils");
var BaseServices = require("../../services/BaseServices");
var GalleryServices = require("../../services/GalleryServices");

module.exports = function(webot) {
    function add_gallery_start(info, next) {
        var user = info.session.parent || info.session.teacher;
        if (!user) {
            return next(null, "抱歉，您不是认证用户！");
        }

        info.wait("gallery type text");
        return next(null, ejs.render(
            ["分享孩子成长点滴到班级圈，仅供本班所有家长及老师访问。",
             '<a href="<%- url%>">点击这里使用网页版分享。',
             "使用微信对话框分享，请点击左下侧键盘图标后输入文字"].join("\n\n"),
            {
                url: conf.site_root + '/webot/wap/index.html?' +
                    BaseServices.getAuthoriedParams(info.session.school.id, user.id)
            }
        ));
    }
    // 等待主题输入
    webot.waitRule('gallery type text', function(info, next) {
        if (info.is("event")) {
            return next();
        }
        if (!info.is("text")) {
            utils.operation_is_failed(info, next);
            info.rewait("gallery type text");
            return next(null, "抱歉，只能输入文字。");
        }
        // 构造image
        info.session.gallery = {title: '', photos: []};
        info.session.gallery.title = info.text;

        info.wait("gallery type image or confirm");
        return next(null, "如需发布文字记录：\n回复【" + wxconst.YES + "】提交\n回复【" + wxconst.NO + "】取消\n\n如需发布图文记录，请上传图片。");
    });

    webot.waitRule('gallery type image or confirm', function(info, next) {
        if (info.is("event")) {
            delete info.session.gallery;
            return next();
        }

        var user = info.session.parent || info.session.teacher;

        // 接受提交指令
        if (info.is("text")) {
            if (info.text === wxconst.YES) {
                // 图片入库
                var time = (new Date()).getTime(), map = {}, photos = [];
                for (var i=0; i<info.session.gallery.photos.length; i++) {
                    var filename = '/school/' +  info.session.school.id + '/user/' + user.id + '/photo/' + (new Date()).getTime() + '/' + i;

                    if (map[info.session.gallery.photos[i]]) { continue; }

                    map[info.session.gallery.photos[i]] = filename;
                    photos.push(filename);
                    utils.download_image(info.session.gallery.photos[i], filename);
                }
                info.session.gallery.photos = photos;
                // 创建者
                info.session.gallery.createdBy = user.id;

                var derfer = '', classId = '';
                if (info.session.parent) {
                    derfer = GalleryServices.createAsParent(info.session.school.id, info.session.parent.students[0].id, info.session.gallery);
                    classId = info.session.parent.students[0].classId;
                } else if (info.session.teacher) {
                    derfer = GalleryServices.createAsTeacher(info.session.school.id, info.session.teacher.wexClasses[0].id, info.session.gallery);
                    classId = info.session.teacher.wexClasses[0].id;
                }

                derfer.then(function() {
                    delete info.session.gallery;

                    return next(null, ejs.render(
                        '成功提交!\n<a href="<%- url%>">点击这里查看。',
                        {
                            url: conf.site_root + '/webot/wap/index.html?' +
                                    BaseServices.getAuthoriedParams(info.session.school.id, user.id) +
                                    '#/class/' + classId + '/gallery'
                        }
                    ));
                }, function(err) {
                    delete info.session.gallery;
                    next(null, "抱歉，服务器异常，无法提交。");
                });
            } else if (info.text === wxconst.NO) {
                delete info.session.gallery;
                return next(null, "操作已取消。");
            } else {
                utils.operation_is_failed(info, next);
                info.rewait("gallery type image or confirm");
                return next(null, "只能回复【" + wxconst.YES + "】或者【" + wxconst.NO + "】，或者上传图片。");
            }
        } else if (info.is("image")) {
            // 构造image
            if (info.session.gallery) {
                info.session.gallery.photos.push(info.param.picUrl);
            }
            info.rewait("gallery type image or confirm");
            var len = info.session.gallery.photos.length;
            return next(null, "已存草稿图片" + len + "张，您可继续上传图片。\n\n回复【" + wxconst.YES + "】发布\n回复【" + wxconst.NO + "】取消");
        } else {
            utils.operation_is_failed(info, next);
            info.rewait("gallery type image or confirm");
            return next(null, "只能回复【" + wxconst.YES + "】或者【" + wxconst.NO + "】，或者上传图片。");
        }
    });

    function view_gallery(info, next) {
        var user = info.session.parent || info.session.teacher;
        if (!user) {
            return next(null, "抱歉，您不是认证用户！");
        }
        var classId = '';
        if (info.session.parent) {
            classId = info.session.parent.students[0].classId;
        } else if (info.session.teacher) {
            classId = info.session.teacher.wexClasses[0].id;
        }
        return next(null, ejs.render(
            '请<a href="<%- url%>">点击这里查看',
            {
                url: conf.site_root + '/webot/wap/index.html?' +
                        BaseServices.getAuthoriedParams(info.session.school.id, user.id) +
                        '#/class/' + classId + '/gallery'
            }
        ));
    }

    // 发布图片提示语
    webot.set('user gallery add start by text', {
        domain: "gateway",
        pattern: /^发布班级圈记录/i,
        handler: add_gallery_start
    });
    webot.set('user gallery add start by event', {
        domain: "gateway",
        pattern: function(info) {
            return info.param.eventKey === 'IMAGE_ADD';
        },
        handler: add_gallery_start
    });

    // 查看图片
    webot.set('user gallery view start by text', {
        domain: "gateway",
        pattern: /^(班级圈|我的班级圈)/i,
        handler: view_gallery
    });
    webot.set('user gallery view start by event', {
        domain: "gateway",
        pattern: function(info) {
            return info.param.eventKey === 'IMAGE_VIEW';
        },
        handler: view_gallery
    });
}<|endoftext|>


<commit_before>const async = require('async')
const exec = require('child_process').exec
const fs = require('fs-extra')
const gm = require('gm')
const path = require('path')

exports.sizes = {
  thumb: 120,
  large: 1000
}

exports.copy = function (task, callback) {
  fs.copy(task.src, task.dest, callback)
}

// Small square photo thumbnail
exports.photoSquare = function (task, callback) {
  gm(task.src)
    .autoOrient()
    .coalesce()
    .resize(exports.sizes.thumb, exports.sizes.thumb, '^')
    .gravity('Center')
    .crop(exports.sizes.thumb, exports.sizes.thumb)
    .quality(90)
    .write(task.dest, callback)
}

// Large photo
exports.photoLarge = function (task, callback) {
  gm(task.src)
    .autoOrient()
    .resize(null, exports.sizes.large, '>')
    .quality(90)
    .write(task.dest, callback)
}

// Web-streaming friendly video
exports.videoWeb = function (task, callback) {
  var ffmpeg = 'ffmpeg -i "' + task.src + '" -y "' + task.dest + '" -f mp4 -vcodec libx264 -ab 96k'
  // AVCHD/MTS videos need a full-frame export to avoid interlacing artefacts
  if (path.extname(task.src).toLowerCase() === '.mts') {
    ffmpeg += ' -vf yadif=1 -qscale:v 4'
  } else {
    ffmpeg += ' -vb 1200k'
  }
  exec(ffmpeg, callback)
}

// Large video preview (before you click play)
exports.videoLarge = function (task, callback) {
  async.series([
    function (next) {
      extractFrame(task, next)
    },
    function (next) {
      exports.photoLarge({
        src: task.dest,
        dest: task.dest
      }, next)
    }
  ], callback)
}

// Small square video preview
exports.videoSquare = function (task, callback) {
  async.series([
    function (next) {
      extractFrame(task, next)
    },
    function (next) {
      exports.photoSquare({
        src: task.dest,
        dest: task.dest
      }, next)
    }
  ], callback)
}

function extractFrame (task, callback) {
  const ffmpeg = 'ffmpeg -itsoffset -1 -i "' + task.src + '" -ss 0.1 -vframes 1 -y "' + task.dest + '"'
  exec(ffmpeg, callback)
}
<commit_msg>Fix #63: run ffmpeg using <spawn> and don't buffer stdout/stderr<commit_after>const async = require('async')
const childProcess = require('child_process')
const fs = require('fs-extra')
const gm = require('gm')
const path = require('path')

exports.sizes = {
  thumb: 120,
  large: 1000
}

exports.copy = function (task, callback) {
  fs.copy(task.src, task.dest, callback)
}

// Small square photo thumbnail
exports.photoSquare = function (task, callback) {
  gm(task.src)
    .autoOrient()
    .coalesce()
    .resize(exports.sizes.thumb, exports.sizes.thumb, '^')
    .gravity('Center')
    .crop(exports.sizes.thumb, exports.sizes.thumb)
    .quality(90)
    .write(task.dest, callback)
}

// Large photo
exports.photoLarge = function (task, callback) {
  gm(task.src)
    .autoOrient()
    .resize(null, exports.sizes.large, '>')
    .quality(90)
    .write(task.dest, callback)
}

// Web-streaming friendly video
exports.videoWeb = function (task, callback) {
  const args = ['-i', task.src, '-y', task.dest, '-f', 'mp4', '-vcodec', 'libx264', '-ab', '96k']
  // AVCHD/MTS videos need a full-frame export to avoid interlacing artefacts
  if (path.extname(task.src).toLowerCase() === '.mts') {
    args.push('-vf', 'yadif=1', '-qscale:v', '4')
  } else {
    args.push('-vb', '1200k')
  }
  exec('ffmpeg', args, callback)
}

// Large video preview (before you click play)
exports.videoLarge = function (task, callback) {
  async.series([
    function (next) {
      extractFrame(task, next)
    },
    function (next) {
      exports.photoLarge({
        src: task.dest,
        dest: task.dest
      }, next)
    }
  ], callback)
}

// Small square video preview
exports.videoSquare = function (task, callback) {
  async.series([
    function (next) {
      extractFrame(task, next)
    },
    function (next) {
      exports.photoSquare({
        src: task.dest,
        dest: task.dest
      }, next)
    }
  ], callback)
}

function extractFrame (task, callback) {
  const args = ['-itsoffset', '-1', '-i', task.src, '-ss', '0.1', '-vframes', 1, '-y', task.dest]
  exec('ffmpeg', args, callback)
}

function exec (command, args, callback) {
  const child = childProcess.spawn(command, args, {
    stdio: 'ignore'
  })
  child.on('error', err => callback(err))
  child.on('exit', (code, signal) => {
    if (code > 0) callback(new Error(`${command} exited with code ${code}`))
    else callback(null)
  })
}
<|endoftext|>


<commit_before>/* This module exports the `makeBundle` function that is used to
 * bundle a web page and its resources into data-URIs.
 */

var mime = require('mime');
var async = require('async');
var request = require('superagent');
var cheerio = require('cheerio');
var urllib = require('url');

// Map selectors for cheerio instances to handlers.
var bundleable = {
  'img': replaceImages,
  'link[rel="stylesheet"]': replaceCSSFiles,
  'script': replaceJSFiles
};

function replaceResources(url, html, callback) {
  var $ = cheerio.load(html);
  var selectors = Object.keys(bundleable);
  var functions = [];
  for (var i = 0, len = selectors.length; i < len; ++i) {
    functions.push(function (selector) {
      return function (callback) {
        console.log(selector);
        bundleable[selector]($, selector, url, callback);
      };
    }(selectors[i]));
  }
  async.series(functions, function (err, cheerios) {
    if (err) {
      callback(err, null);
    } else {
      callback(null, cheerios[cheerios.length - 1].html());
    }
  });
}

function dataURI(url, content) {
  var encoded = content.toString('base64');
  return 'data:' + mime.lookup(url) + ';base64,' + encoded;
}

function fetchAndReplace(attr, elem, url, callback) {
  var resource = elem.attr(attr);
  if (typeof resource === 'undefined' || !resource) {
    return;
  }
  url = urllib.resolve(url, resource);
  request.get(url).end(function (err, result) {
    if (!err) {
      var newuri = dataURI(url, result.body);
      console.log('Computed data uri ' + newuri);
      elem.attr(attr, newuri);
      console.log('Replaced URL');
      callback(null, elem);
    } else {
      callback(err, null);
    }
  });
}

function replaceAll($, selector, url, attr, callback) {
  var elements = [];
  $(selector).each(function (index, elem) {
    var $_this = $(this);
    elements.push($_this);
  });
  async.reduce(elements, elements[0], function (memo, item, next) {
    fetchAndReplace(attr, memo, url, next);
  }, callback);
}

function replaceImages($, selector, url, callback) {
  replaceAll($, selector, url, 'src', callback);
}

function replaceCSSFiles($, selector, url, callback) {
  replaceAll($, selector, url, 'href', callback);
}

function replaceJSFiles($, selector, url, callback) {
  replaceAll($, selector, url, 'src', callback);
}

module.exports = {
  makeBundle: function (url, callback) {
    request.get(url).end(function (err, result) {
      if (err) {
        callback(err, null);
      } else {
        replaceResources(url, result.text, callback);
      }
    });
  }
};
<commit_msg>Do not try to fetch elements that do not refer to other documents<commit_after>/* This module exports the `makeBundle` function that is used to
 * bundle a web page and its resources into data-URIs.
 */

var mime = require('mime');
var async = require('async');
var request = require('superagent');
var cheerio = require('cheerio');
var urllib = require('url');

// Map selectors for cheerio instances to handlers.
var bundleable = {
  'img': replaceImages,
  'link[rel="stylesheet"]': replaceCSSFiles,
  'script': replaceJSFiles
};

function replaceResources(url, html, callback) {
  var $ = cheerio.load(html);
  var selectors = Object.keys(bundleable);
  var functions = [];
  for (var i = 0, len = selectors.length; i < len; ++i) {
    functions.push(function (selector) {
      return function (callback) {
        console.log('Looking for items with selector ' + selector);
        bundleable[selector]($, selector, url, callback);
      };
    }(selectors[i]));
  }
  async.series(functions, function (err, cheerios) {
    if (err) {
      callback(err, null);
    } else {
      console.log('Finished calling series of handlers');
      callback(null, cheerios[cheerios.length - 1].html());
    }
  });
}

function dataURI(url, content) {
  var encoded = content.toString('base64');
  return 'data:' + mime.lookup(url) + ';base64,' + encoded;
}

function fetchAndReplace(attr, elem, url, callback) {
  var resource = elem.attr(attr);
  if (typeof resource === 'undefined' || !resource) {
    return;
  }
  url = urllib.resolve(url, resource);
  request.get(url).end(function (err, result) {
    if (!err) {
      var newuri = dataURI(url, result.body);
      console.log('Computed data uri ' + newuri);
      elem.attr(attr, newuri);
      console.log('Replaced URL');
      callback(null, elem);
    } else {
      callback(err, null);
    }
  });
}

function replaceAll($, selector, url, attr, callback) {
  var elements = [];
  $(selector).each(function (index, elem) {
    var $_this = $(this);
    elements.push($_this);
  });
  async.reduce(elements, elements[0], function (memo, item, next) {
    console.log('Reducing ' + elements.length + ' elements');
    if (typeof memo.attr(attr) === 'undefined') {
      console.log('Skipping element');
      next(null, item);
    } else {
      console.log('Processing new element');
      fetchAndReplace(attr, memo, url, next);
    }
  }, callback);
}

function replaceImages($, selector, url, callback) {
  replaceAll($, selector, url, 'src', callback);
}

function replaceCSSFiles($, selector, url, callback) {
  replaceAll($, selector, url, 'href', callback);
}

function replaceJSFiles($, selector, url, callback) {
  replaceAll($, selector, url, 'src', callback);
}

module.exports = {
  makeBundle: function (url, callback) {
    request.get(url).end(function (err, result) {
      if (err) {
        callback(err, null);
      } else {
        replaceResources(url, result.text, callback);
      }
    });
  }
};
<|endoftext|>


<commit_before>// Copyright © 2014 Intel Corporation. All rights reserved.
// Use  of this  source  code is  governed by  an Apache v2
// license that can be found in the LICENSE-APACHE-V2 file.

var Path = require('path');
var ShellJS = require("shelljs");
var AndroidSDK = require("./AndroidSDK");
var Console = require("./Console");
var Project = require("./Project");
var TemplateFile = require("./TemplateFile");

/**
 * Android project class.
 * @throws {@link AndroidSDK~SDKNotFoundError} If the Android SDK was not found in the environment.
 * @constructor
 */
function AndroidProject() {

    this._sdk = new AndroidSDK();
}
AndroidProject.prototype = Project;

AndroidProject.prototype.generateTemplates =
function(packageId, apiTarget, path) {

    var parts = packageId.split('.');
    var packageName = parts[parts.length - 1];
    var data = {
        "packageId" : packageId,
        "packageName" : packageName,
        "apiTarget" : apiTarget
    };

    // AndroidManifest.xml
    var tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "AndroidManifest.xml.tpl");
    tpl.render(data, path + Path.sep + "AndroidManifest.xml");

    // build.xml
    tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "build.xml.tpl");
    tpl.render(data, path + Path.sep + "build.xml");

    // project.properties
    tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "project.properties.tpl");
    tpl.render(data, path + Path.sep + "project.properties");

    // MainActivity.java
    tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "MainActivity.java.tpl");
    var activityPath = path + Path.sep +
                       "src" + Path.sep +
                       parts.join(Path.sep);
    tpl.render(data, activityPath + Path.sep + "MainActivity.java");

    // Copy
    var dirs = ShellJS.ls('crosswalk-*.*.*.*');
    if (dirs.length === 0) {
        Console.error("Unpacked Crosswalk not found in current directory");
        return false;
    }
    var appTplPath = dirs[0];

    // Copy xwalk_core_library
    ShellJS.cp("-r",
               appTplPath + Path.sep + "xwalk_core_library",
               path);

    // Copy jars
    ShellJS.cp(appTplPath + Path.sep + "template" + Path.sep + "libs" + Path.sep + "*.jar",
               path + Path.sep + "libs");

    // Copy res
    ShellJS.cp("-r",
               appTplPath + Path.sep + "template" + Path.sep + "res",
               path);

    // Make html5 app dir
    ShellJS.mkdir("-p", path + Path.sep + "assets" + Path.sep + "www");

    return true;
};

/**
 * Implements {@link Project.generate}
 */
AndroidProject.prototype.generate =
function(packageId, callback) {

    var minApiLevel = 19;
    var apiTarget;
    this._sdk.queryTarget(minApiLevel,
                          function(apiTarget, errormsg) {

        if (errormsg) {
            callback(errormsg);
            return;
        }

        this._sdk.generateProjectTemplate(packageId, apiTarget,
                                          function(path, logmsg, errormsg) {

            if (!path || errormsg) {
                callback(errormsg);
                return;
            }

            var ret = this.generateTemplates(packageId, apiTarget, path);
            if (!ret) {
                callback("Creating project template failed.");
                return;
            }

            Console.log("Project template created at '" + path + "'");
            callback(null);

        }.bind(this));
    }.bind(this));
};

AndroidProject.prototype.update =
function() {

    // TODO implement
};

AndroidProject.prototype.refresh =
function() {

    // TODO implement
};

AndroidProject.prototype.build =
function() {

    // TODO implement
};

module.exports = AndroidProject;
<commit_msg>AndroidProject: Also search for crosswalk in parent directory<commit_after>// Copyright © 2014 Intel Corporation. All rights reserved.
// Use  of this  source  code is  governed by  an Apache v2
// license that can be found in the LICENSE-APACHE-V2 file.

var Path = require('path');
var ShellJS = require("shelljs");
var AndroidSDK = require("./AndroidSDK");
var Console = require("./Console");
var Project = require("./Project");
var TemplateFile = require("./TemplateFile");

/**
 * Android project class.
 * @throws {@link AndroidSDK~SDKNotFoundError} If the Android SDK was not found in the environment.
 * @constructor
 */
function AndroidProject() {

    this._sdk = new AndroidSDK();
}
AndroidProject.prototype = Project;

AndroidProject.prototype.generateTemplates =
function(packageId, apiTarget, path) {

    var parts = packageId.split('.');
    var packageName = parts[parts.length - 1];
    var data = {
        "packageId" : packageId,
        "packageName" : packageName,
        "apiTarget" : apiTarget
    };

    // AndroidManifest.xml
    var tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "AndroidManifest.xml.tpl");
    tpl.render(data, path + Path.sep + "AndroidManifest.xml");

    // build.xml
    tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "build.xml.tpl");
    tpl.render(data, path + Path.sep + "build.xml");

    // project.properties
    tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "project.properties.tpl");
    tpl.render(data, path + Path.sep + "project.properties");

    // MainActivity.java
    tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "MainActivity.java.tpl");
    var activityPath = path + Path.sep +
                       "src" + Path.sep +
                       parts.join(Path.sep);
    tpl.render(data, activityPath + Path.sep + "MainActivity.java");

    // Copy
    var dirs = ShellJS.ls('crosswalk-*.*.*.*');
    if (dirs.length === 0) {
        // Also try parent dir.
        // This is especially useful for tests that run in a temporary dir.
        dirs = ShellJS.ls('../crosswalk-*.*.*.*');
        if (dirs.length === 0) {
            Console.error("Unpacked Crosswalk not found in current or parent directory " + ShellJS.pwd());
            return false;
        }
    }
    var appTplPath = dirs[0];

    // Copy xwalk_core_library
    ShellJS.cp("-r",
               appTplPath + Path.sep + "xwalk_core_library",
               path);

    // Copy jars
    ShellJS.cp(appTplPath + Path.sep + "template" + Path.sep + "libs" + Path.sep + "*.jar",
               path + Path.sep + "libs");

    // Copy res
    ShellJS.cp("-r",
               appTplPath + Path.sep + "template" + Path.sep + "res",
               path);

    // Make html5 app dir
    ShellJS.mkdir("-p", path + Path.sep + "assets" + Path.sep + "www");

    return true;
};

/**
 * Implements {@link Project.generate}
 */
AndroidProject.prototype.generate =
function(packageId, callback) {

    var minApiLevel = 19;
    var apiTarget;
    this._sdk.queryTarget(minApiLevel,
                          function(apiTarget, errormsg) {

        if (errormsg) {
            callback(errormsg);
            return;
        }

        this._sdk.generateProjectTemplate(packageId, apiTarget,
                                          function(path, logmsg, errormsg) {

            if (!path || errormsg) {
                callback(errormsg);
                return;
            }

            var ret = this.generateTemplates(packageId, apiTarget, path);
            if (!ret) {
                callback("Creating project template failed.");
                return;
            }

            Console.log("Project template created at '" + path + "'");
            callback(null);

        }.bind(this));
    }.bind(this));
};

AndroidProject.prototype.update =
function() {

    // TODO implement
};

AndroidProject.prototype.refresh =
function() {

    // TODO implement
};

AndroidProject.prototype.build =
function() {

    // TODO implement
};

module.exports = AndroidProject;
<|endoftext|>


<commit_before>import React from 'react';
import Footer from '../../components/safety/footer.js';
import Header from '../../components/safety/header.js';
import GalleryItemHeader from '../../components/safety/gallery-item-header.js';
import DisplayItem from '../../components/safety/display-item.js';
import Modal from '../../components/safety/modal.js';
import SignupForm from '../../components/safety/signup-form.js';
import SignupCta from '../../components/safety/signup-cta.js';
import SafetyGallery from '../../components/safety/safety-gallery.js';
import GalleryItem from '../../components/safety/gallery-item.js';
import galleryData from '../../components/safety/gallery-data.js';
import NextVideo from '../../components/safety/next-video.js';
import StickyContainer from '../../components/signup-form/sticky-container.js';
import reactGA from 'react-ga';

var Safety = React.createClass({
  getInitialState: function() {
    return {
      showModal: false,
      cancelTimeout: false,
      signupSuccess: false,
      videoStatus: "stopped"
    };
  },
  componentDidMount: function() {
    const video = this.props.params.video;

    if (!video) {
      setTimeout(() => {
        if (!this.state.cancelTimeout) {
          this.openModal();
        }
      }, 3000);
    }
  },
  componentWillReceiveProps: function() {
    this.setState({
      cancelTimeout: true
    });
  },
  closeModal: function() {
    this.setState({
      showModal: false
    });
  },
  openModal: function() {
    this.setState({
      showModal: true,
      cancelTimeout: true
    });
  },
  onSuccess: function() {
    this.setState({
      signupSuccess: true
    });
  },
  getPosition: function() {
    if (!this.stickyContainer || !this.stickyContainer.getClientRects()[0]) {
      return 0;
    }
    return this.stickyContainer.getClientRects()[0].top + window.scrollY - window.innerHeight + this.stickyContent.children[0].offsetHeight;
  },
  onEnd: function() {
    this.setState({
      videoStatus: "ended"
    });
    reactGA.event({
      category: "Video",
      action: "Video Ended",
      label: this.props.params.video
    });
  },
  onStart: function() {
    if (this.state.videoStatus === "ended") {
      reactGA.event({
        category: "Video",
        action: "Video Replayed",
        label: this.props.params.video
      });
    } else if (this.state.videoStatus === "stopped") {
      reactGA.event({
        category: "Video",
        action: "Video Played",
        label: this.props.params.video
      });
    }

    this.setState({
      videoStatus: "playing"
    });
  },
  onNew: function() {
    this.setState({
      videoStatus: "playing"
    });
  },
  render: function() {
    var className = "safety";
    if (this.props.test) {
      className += " " + this.props.test;
    }

    const video = this.props.params.video;
    var displayItem = null;
    var itemIndex = 0;
    var items = [];
    var currentIndex = 0;

    galleryData.forEach((item, index) => {
      if (video === item.slug) {
        itemIndex = index;
        displayItem = (
          <DisplayItem item={item} itemIndex={itemIndex}
            onEnd={this.onEnd}
            onStart={this.onStart}
            onNew={this.onNew}
            videoStatus={this.state.videoStatus}
          />
        );
        currentIndex = 1;
        if (this.state.videoStatus === "ended") {
          items.splice(0, 0, (

 { this.stickyContainer = element; }}>
                <StickyContainer stickyTo={this.getPosition}>

 { this.stickyContent = element; }}>
                    <NextVideo onClick={this.onNew} itemIndex={itemIndex}/>

                </StickyContainer>

          ));
        } else {
          items.splice(0, 0,

);
        }
      } else {
        items.splice(currentIndex, 0, (
          <GalleryItem key={item.slug} item={item} onClick={this.onNew}/>
        ));
        currentIndex++;
      }
    });

    var header = ();

    if (video) {
      header = (<GalleryItemHeader/>);
    }

    var modal = null;
    if (this.state.showModal) {
      if (this.state.signupSuccess) {
        modal = (
          <Modal onClose={this.closeModal}>

Thanks! Please check your inbox or your spam filter for an email from us to confirm your subscription.

              <button onClick={this.closeModal}>yes, I got it</button>

          </Modal>
        );
      } else {
        modal = (
          <Modal onClose={this.closeModal}>
            <SignupForm onClose={this.closeModal} onSuccess={this.onSuccess}/>
          </Modal>
        );
      }
    }

    if (displayItem) {
      className += " display-item-page";
    } else {
      className += " safety-home-page";
    }

    return (

        {modal}

          {header}
          <SignupCta onCta={this.openModal}/>
          {displayItem}
          <SafetyGallery>
            {items}
          </SafetyGallery>

    );
  }
});

module.exports = Safety;
<commit_msg>Updates to add key to stop complaint.<commit_after>import React from 'react';
import Footer from '../../components/safety/footer.js';
import Header from '../../components/safety/header.js';
import GalleryItemHeader from '../../components/safety/gallery-item-header.js';
import DisplayItem from '../../components/safety/display-item.js';
import Modal from '../../components/safety/modal.js';
import SignupForm from '../../components/safety/signup-form.js';
import SignupCta from '../../components/safety/signup-cta.js';
import SafetyGallery from '../../components/safety/safety-gallery.js';
import GalleryItem from '../../components/safety/gallery-item.js';
import galleryData from '../../components/safety/gallery-data.js';
import NextVideo from '../../components/safety/next-video.js';
import StickyContainer from '../../components/signup-form/sticky-container.js';
import reactGA from 'react-ga';

var Safety = React.createClass({
  getInitialState: function() {
    return {
      showModal: false,
      cancelTimeout: false,
      signupSuccess: false,
      videoStatus: "stopped"
    };
  },
  componentDidMount: function() {
    const video = this.props.params.video;

    if (!video) {
      setTimeout(() => {
        if (!this.state.cancelTimeout) {
          this.openModal();
        }
      }, 3000);
    }
  },
  componentWillReceiveProps: function() {
    this.setState({
      cancelTimeout: true
    });
  },
  closeModal: function() {
    this.setState({
      showModal: false
    });
  },
  openModal: function() {
    this.setState({
      showModal: true,
      cancelTimeout: true
    });
  },
  onSuccess: function() {
    this.setState({
      signupSuccess: true
    });
  },
  getPosition: function() {
    if (!this.stickyContainer || !this.stickyContainer.getClientRects()[0]) {
      return 0;
    }
    return this.stickyContainer.getClientRects()[0].top + window.scrollY - window.innerHeight + this.stickyContent.children[0].offsetHeight;
  },
  onEnd: function() {
    this.setState({
      videoStatus: "ended"
    });
    reactGA.event({
      category: "Video",
      action: "Video Ended",
      label: this.props.params.video
    });
  },
  onStart: function() {
    if (this.state.videoStatus === "ended") {
      reactGA.event({
        category: "Video",
        action: "Video Replayed",
        label: this.props.params.video
      });
    } else if (this.state.videoStatus === "stopped") {
      reactGA.event({
        category: "Video",
        action: "Video Played",
        label: this.props.params.video
      });
    }

    this.setState({
      videoStatus: "playing"
    });
  },
  onNew: function() {
    this.setState({
      videoStatus: "playing"
    });
  },
  render: function() {
    var className = "safety";
    if (this.props.test) {
      className += " " + this.props.test;
    }

    const video = this.props.params.video;
    var displayItem = null;
    var itemIndex = 0;
    var items = [];
    var currentIndex = 0;

    galleryData.forEach((item, index) => {
      if (video === item.slug) {
        itemIndex = index;
        displayItem = (
          <DisplayItem item={item} itemIndex={itemIndex}
            onEnd={this.onEnd}
            onStart={this.onStart}
            onNew={this.onNew}
            videoStatus={this.state.videoStatus}
          />
        );
        currentIndex = 1;
        if (this.state.videoStatus === "ended") {
          items.splice(0, 0, (

 { this.stickyContainer = element; }}>
                <StickyContainer stickyTo={this.getPosition}>

 { this.stickyContent = element; }}>
                    <NextVideo onClick={this.onNew} itemIndex={itemIndex}/>

                </StickyContainer>

          ));
        } else {
          items.splice(0, 0,

);
        }
      } else {
        items.splice(currentIndex, 0, (
          <GalleryItem key={item.slug} item={item} onClick={this.onNew}/>
        ));
        currentIndex++;
      }
    });

    var header = ();

    if (video) {
      header = (<GalleryItemHeader/>);
    }

    var modal = null;
    if (this.state.showModal) {
      if (this.state.signupSuccess) {
        modal = (
          <Modal onClose={this.closeModal}>

Thanks! Please check your inbox or your spam filter for an email from us to confirm your subscription.

              <button onClick={this.closeModal}>yes, I got it</button>

          </Modal>
        );
      } else {
        modal = (
          <Modal onClose={this.closeModal}>
            <SignupForm onClose={this.closeModal} onSuccess={this.onSuccess}/>
          </Modal>
        );
      }
    }

    if (displayItem) {
      className += " display-item-page";
    } else {
      className += " safety-home-page";
    }

    return (

        {modal}

          {header}
          <SignupCta onCta={this.openModal}/>
          {displayItem}
          <SafetyGallery>
            {items}
          </SafetyGallery>

    );
  }
});

module.exports = Safety;
<|endoftext|>


<commit_before>function identity (x) {
  return x
}

module.exports = {
  name: 'AnimatedNumber',
  functional: true,
  props: {
    number: Number,
    formatter: {
      type: Function,
      required: false,
      default: function () {
        return identity
      }
    }
  },
  data: function () {
    return {
      display: this.number,
      interval: false
    }
  },
  render: function (h) {
    return h('span', this.formatter(this.display))
  },
  watch: {
    number: function (val, old) {
      const $vm = this
      $vm.interval && clearInterval($vm.interval)

      let speed = 10
      if (val < old) {
        speed = 4
      }

      $vm.interval = setInterval(function () {
        if ($vm.display !== $vm.number) {
          const diff = ($vm.number - $vm.display) / speed
          $vm.display += diff >= 0 ? Math.ceil(diff) : Math.floor(diff)
        } else {
          clearInterval($vm.interval)
        }
      }, 20)
    }
  }
}
<commit_msg>Remove functional property from component<commit_after>function identity (x) {
  return x
}

module.exports = {
  name: 'AnimatedNumber',
  props: {
    number: Number,
    formatter: {
      type: Function,
      required: false,
      default: function () {
        return identity
      }
    }
  },
  data: function () {
    return {
      display: this.number,
      interval: false
    }
  },
  render: function (h) {
    return h('span', this.formatter(this.display))
  },
  watch: {
    number: function (val, old) {
      const $vm = this
      $vm.interval && clearInterval($vm.interval)

      let speed = 10
      if (val < old) {
        speed = 4
      }

      $vm.interval = setInterval(function () {
        if ($vm.display !== $vm.number) {
          const diff = ($vm.number - $vm.display) / speed
          $vm.display += diff >= 0 ? Math.ceil(diff) : Math.floor(diff)
        } else {
          clearInterval($vm.interval)
        }
      }, 20)
    }
  }
}
<|endoftext|>


<commit_before>import './AssetFactory'

import './Commands/PublishCommand'
import './Commands/UnpublishCommand'

import './Compilers/BabelCompiler'
import './Compilers/CssCompiler'
import './Compilers/JavascriptCompiler'
import './Compilers/RawCompiler'
import './Compilers/ScssCompiler'
import './Compilers/SvgCompiler'

import './Controllers/CompileController'

import './View/AssetContainer'
import './View/AssetExtension'

import path from 'path'
import express from 'express'

function expandMacros(config, macros) {
	const isArray = Array.isArray(config)
	let reindex = false

	for(const key of Object.keys(config)) {
		const value = config[key]

		if(typeof value === 'object' && value !== null) {
			config[key] = expandMacros(value, macros)
			continue
		} else if(typeof value !== 'string') {
			continue
		}

		for(const macro of macros) {
			const pattern = macro[0]
			const replacement = macro[1]

			if(!replacement.isNil) {
				config[key] = value.replace(pattern, replacement)
			} else if(value.match(pattern)) {
				reindex = isArray
				delete config[key]
			}
		}
	}

	if(reindex) {
		config = Array.from(config).filter(value => value.isNil === false)
	}

	return config
}

export function AssetsProvider(app, parameters = { }) {
	app.config.loadDefault('assets', path.join(__dirname, '../config/assets.json'))
	let config = Object.assign({ }, app.config.get('assets'))

	const macros = [
		[ 'node_modules', config.node_modules || app.paths.base('node_modules') ]
	]

	for(const macro of macros) {
		macro[0] = new RegExp(`\\{\\{\\s*${macro[0]}\\s*\\}\\}`, 'g')
	}

	config = expandMacros(config, macros)
	app.config.set('assets', config)

	const autoMinify = !config.auto_minify ? config.auto_minify : !app.debug
	const factory = new AssetFactory(app, autoMinify)
	app.assets = factory

	factory.registerCompiler(BabelCompiler)
	factory.registerCompiler(CssCompiler)
	factory.registerCompiler(JavascriptCompiler)
	factory.registerCompiler(RawCompiler)
	factory.registerCompiler(ScssCompiler)
	factory.registerCompiler(SvgCompiler)

	app.routes.group({ prefix: 'assets', controller: new CompileController(app, factory) }, routes => {
		routes.get(':type/:a?/:b?/:c?/:d?/:e?', 'compile')
	})

	const dirs = new Set()
	const published = app.config.get('assets-published', { })
	for(const src of Object.keys(published)) {
		dirs.add(published[src].replace(/^\//, '').split(/\//)[0])
	}

	for(const dir of dirs) {
		app.use(`/${dir}`, express.static(app.paths.public(dir)))
	}

	if(!app.cli.isNil) {
		app.cli.register(PublishCommand)
		app.cli.register(UnpublishCommand)
	}

	if(!app.view.isNil) {
		if(!app.html.isNil) {
			const assetExtensionClass = parameters.assetExtensionClass || AssetExtension
			app.view.addExtension('AssetExtension', new assetExtensionClass)
		}

		const assetContainerClass = parameters.assetContainerClass || AssetContainer

		app.use((req, res, next) => {
			res.locals.assetPath = (path, secure) => factory.publishedPath(path, req, secure)

			if(!res.locals.html.isNil) {
				res.locals._assetContainer = new assetContainerClass(req, res, factory, res.locals.html)
			}

			next()
		})
	}
}

AssetsProvider.priority = 10000
<commit_msg>Updating for grind 0.5 routing changes<commit_after>import './AssetFactory'

import './Commands/PublishCommand'
import './Commands/UnpublishCommand'

import './Compilers/BabelCompiler'
import './Compilers/CssCompiler'
import './Compilers/JavascriptCompiler'
import './Compilers/RawCompiler'
import './Compilers/ScssCompiler'
import './Compilers/SvgCompiler'

import './Controllers/CompileController'

import './View/AssetContainer'
import './View/AssetExtension'

import path from 'path'

function expandMacros(config, macros) {
	const isArray = Array.isArray(config)
	let reindex = false

	for(const key of Object.keys(config)) {
		const value = config[key]

		if(typeof value === 'object' && value !== null) {
			config[key] = expandMacros(value, macros)
			continue
		} else if(typeof value !== 'string') {
			continue
		}

		for(const macro of macros) {
			const pattern = macro[0]
			const replacement = macro[1]

			if(!replacement.isNil) {
				config[key] = value.replace(pattern, replacement)
			} else if(value.match(pattern)) {
				reindex = isArray
				delete config[key]
			}
		}
	}

	if(reindex) {
		config = Array.from(config).filter(value => value.isNil === false)
	}

	return config
}

export function AssetsProvider(app, parameters = { }) {
	app.config.loadDefault('assets', path.join(__dirname, '../config/assets.json'))
	let config = Object.assign({ }, app.config.get('assets'))

	const macros = [
		[ 'node_modules', config.node_modules || app.paths.base('node_modules') ]
	]

	for(const macro of macros) {
		macro[0] = new RegExp(`\\{\\{\\s*${macro[0]}\\s*\\}\\}`, 'g')
	}

	config = expandMacros(config, macros)
	app.config.set('assets', config)

	const autoMinify = !config.auto_minify ? config.auto_minify : !app.debug
	const factory = new AssetFactory(app, autoMinify)
	app.assets = factory

	factory.registerCompiler(BabelCompiler)
	factory.registerCompiler(CssCompiler)
	factory.registerCompiler(JavascriptCompiler)
	factory.registerCompiler(RawCompiler)
	factory.registerCompiler(ScssCompiler)
	factory.registerCompiler(SvgCompiler)

	app.routes.group({ prefix: 'assets', controller: new CompileController(app, factory) }, routes => {
		routes.get(':type/:a?/:b?/:c?/:d?/:e?', 'compile')
	})

	const dirs = new Set()
	const published = app.config.get('assets-published', { })
	for(const src of Object.keys(published)) {
		dirs.add(published[src].replace(/^\//, '').split(/\//)[0])
	}

	for(const dir of dirs) {
		app.routes.static(dir, dir)
	}

	if(!app.cli.isNil) {
		app.cli.register(PublishCommand)
		app.cli.register(UnpublishCommand)
	}

	if(!app.view.isNil) {
		if(!app.html.isNil) {
			const assetExtensionClass = parameters.assetExtensionClass || AssetExtension
			app.view.addExtension('AssetExtension', new assetExtensionClass)
		}

		const assetContainerClass = parameters.assetContainerClass || AssetContainer

		app.routes.use((req, res, next) => {
			res.locals.assetPath = (path, secure) => factory.publishedPath(path, req, secure)

			if(!res.locals.html.isNil) {
				res.locals._assetContainer = new assetContainerClass(req, res, factory, res.locals.html)
			}

			next()
		})
	}
}

AssetsProvider.priority = 10000
<|endoftext|>


<commit_before>define([
    'jquery',
    'json.sortify',
    '/bower_components/nthen/index.js',
    '/common/sframe-common.js',
    '/common/sframe-app-framework.js',
    '/common/common-util.js',
    '/common/common-hash.js',
    '/common/common-interface.js',
    '/common/modes.js',
    '/customize/messages.js',
    '/kanban/jkanban.js',
    '/common/jscolor.js',
    'css!/kanban/jkanban.css',

    'less!/kanban/app-kanban.less'
], function (
    $,
    Sortify,
    nThen,
    SFCommon,
    Framework,
    Util,
    Hash,
    UI,
    Modes,
    Messages)
{

    var verbose = function (x) { console.log(x); };
    verbose = function () {}; // comment out to enable verbose logging

    var COLORS = ['yellow', 'green', 'orange', 'blue', 'red', 'purple', 'cyan', 'lightgreen', 'lightblue'];

    var addRemoveItemButton = function (framework, kanban) {
        if (!kanban) { return; }
        if (framework.isReadOnly() || framework.isLocked()) { return; }
        var $container = $(kanban.element);
        $container.find('.kanban-remove-item').remove();
        $container.find('.kanban-board .kanban-item').each(function (i, el) {
            var pos = kanban.findElementPosition(el);
            var board = kanban.options.boards.find(function (b) {
                return b.id === $(el.parentNode.parentNode).attr('data-id');
            });
            $('<button>', {
                'class': 'kanban-remove-item btn btn-default fa fa-times',
                title: Messages.kanban_removeItem
            }).click(function (e) {
                e.stopPropagation();
                UI.confirm(Messages.kanban_removeItemConfirm, function (yes) {
                    if (!yes) { return; }
                    board.item.splice(pos, 1);
                    $(el).remove();
                    kanban.onChange();
                });
            }).appendTo($(el));
        });
    };

    // Kanban code
    var initKanban = function (framework, boards) {
        var defaultBoards = [{
            "id": "todo",
            "title": Messages.kanban_todo,
            "color": "blue",
            "item": [{
                "title": Messages._getKey('kanban_item', [1])
            }, {
                "title": Messages._getKey('kanban_item', [2])
            }]
        }, {
            "id": "working",
            "title": Messages.kanban_working,
            "color": "orange",
            "item": [{
                "title": Messages._getKey('kanban_item', [3])
            }, {
                "title": Messages._getKey('kanban_item', [4])
            }]
        }, {
            "id": "done",
            "title": Messages.kanban_done,
            "color": "green",
            "item": [{
                "title": Messages._getKey('kanban_item', [5])
            }, {
                "title": Messages._getKey('kanban_item', [6])
            }]
        }];

        if (!boards) {
            verbose("Initializing with default boards content");
            boards = defaultBoards;
        } else {
            verbose("Initializing with boards content " + boards);
        }

        // Remove any existing elements
        $(".kanban-container-outer").remove();

        var getInput = function () {
            return $('<input>', {
                'type': 'text',
                'id': 'kanban-edit',
                'size': '30'
            }).click(function (e) { e.stopPropagation(); });
        };

        var kanban = new window.jKanban({
            element: '#cp-app-kanban-content',
            gutter: '5px',
            widthBoard: '300px',
            buttonContent: '❌',
            colors: COLORS,
            readOnly: framework.isReadOnly(),
            onChange: function () {
                verbose("Board object has changed");
                framework.localChange();
                if (kanban) {
                    addRemoveItemButton(framework, kanban);
                }
            },
            click: function (el) {
                if (framework.isReadOnly() || framework.isLocked()) { return; }
                if (kanban.inEditMode) {
                    $(el).focus();
                    verbose("An edit is already active");
                    //return;
                }
                kanban.inEditMode = true;
                $(el).find('button').remove();
                var name = $(el).text();
                $(el).html('');
                var $input = getInput().val(name).appendTo(el).focus();
                $input[0].select();
                var save = function () {
                    // Store the value
                    var name = $input.val();
                    // Remove the input
                    $(el).text(name);
                    // Save the value for the correct board
                    var board = $(el).closest('.kanban-board').attr("data-id");
                    var pos = kanban.findElementPosition(el.parentNode);
                    kanban.getBoardJSON(board).item[pos].title = name;
                    kanban.onChange();
                    // Unlock edit mode
                    kanban.inEditMode = false;
                };
                $input.blur(save);
                $input.keydown(function (e) {
                    if (e.which === 13) {
                        e.preventDefault();
                        e.stopPropagation();
                        save();
                        if (!$input.val()) { return; }
                        if (!$(el).closest('.kanban-item').is(':last-child')) { return; }
                        $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click();
                        return;
                    }
                    if (e.which === 27) {
                        e.preventDefault();
                        e.stopPropagation();
                        $(el).text(name);
                        kanban.inEditMode = false;
                        addRemoveItemButton(framework, kanban);
                        return;
                    }
                });

            },
            boardTitleClick: function (el, e) {
                e.stopPropagation();
                if (framework.isReadOnly() || framework.isLocked()) { return; }
                if (kanban.inEditMode) {
                    $(el).focus();
                    verbose("An edit is already active");
                    //return;
                }
                kanban.inEditMode = true;
                var name = $(el).text();
                $(el).html('');
                var $input = getInput().val(name).appendTo(el).focus();
                $input[0].select();
                var save = function () {
                    // Store the value
                    var name = $input.val();
                    // Remove the input
                    $(el).text(name);
                    // Save the value for the correct board
                    var board = $(el.parentNode.parentNode).attr("data-id");
                    kanban.getBoardJSON(board).title = name;
                    kanban.onChange();
                    // Unlock edit mode
                    kanban.inEditMode = false;
                };
                $input.blur(save);
                $input.keydown(function (e) {
                    if (e.which === 13) {
                        e.preventDefault();
                        e.stopPropagation();
                        save();
                        return;
                    }
                    if (e.which === 27) {
                        e.preventDefault();
                        e.stopPropagation();
                        $(el).text(name);
                        kanban.inEditMode = false;
                        return;
                    }
                });
            },
            colorClick: function (el, type) {
                if (framework.isReadOnly() || framework.isLocked()) { return; }
                verbose("on color click");
                var boardJSON;
                var board;
                if (type === "board") {
                    verbose("board color click");
                    board = $(el.parentNode).attr("data-id");
                    boardJSON = kanban.getBoardJSON(board);
                } else {
                    verbose("item color click");
                    board = $(el.parentNode.parentNode).attr("data-id");
                    var pos = kanban.findElementPosition(el);
                    boardJSON = kanban.getBoardJSON(board).item[pos];
                }
                var onchange = function (colorL) {
                    var elL = el;
                    var typeL = type;
                    var boardJSONL;
                    var boardL;
                    if (typeL === "board") {
                        verbose("board color change");
                        boardL = $(elL.parentNode).attr("data-id");
                        boardJSONL = kanban.getBoardJSON(boardL);
                    } else {
                        verbose("item color change");
                        boardL = $(elL.parentNode.parentNode).attr("data-id");
                        var pos = kanban.findElementPosition(elL);
                        boardJSONL = kanban.getBoardJSON(boardL).item[pos];
                    }
                    var currentColor = boardJSONL.color;
                    verbose("Current color " + currentColor);
                    if (currentColor !== colorL.toString()) {
                        $(elL).removeClass("kanban-header-" + currentColor);
                        boardJSONL.color = colorL.toString();
                        kanban.onChange();
                    }
                };
                var jscolorL;
                el._jscLinkedInstance = undefined;
                jscolorL = new window.jscolor(el,{showOnClick: false, onFineChange: onchange, valueElement:undefined});
                jscolorL.show();
                var currentColor = boardJSON.color;
                if (currentColor === undefined) {
                    currentColor = '';
                }
                jscolorL.fromString(currentColor);
            },
            buttonClick: function (el, boardId, e) {
                e.stopPropagation();
                if (framework.isReadOnly() || framework.isLocked()) { return; }
                UI.confirm(Messages.kanban_deleteBoard, function (yes) {
                    if (!yes) { return; }
                    verbose("Delete board");
                    //var boardName = $(el.parentNode.parentNode).attr("data-id");
                    for (var index in kanban.options.boards) {
                        if (kanban.options.boards[index].id === boardId) {
                            break;
                        }
                        index++;
                    }
                    kanban.options.boards.splice(index, 1);
                    kanban.removeBoard(boardId);
                    kanban.onChange();
                });
            },
            addItemClick: function (el) {
                if (framework.isReadOnly() || framework.isLocked()) { return; }
                if (kanban.inEditMode) {
                    $(el).focus();
                    verbose("An edit is already active");
                    //return;
                }
                kanban.inEditMode = true;
                // create a form to enter element
                var boardId = $(el.parentNode.parentNode).attr("data-id");
                var $item = $('<div>', {'class': 'kanban-item'});
                var $input = getInput().val(name).appendTo($item);
                kanban.addForm(boardId, $item[0]);
                $input.focus();
                var save = function () {
                    $item.remove();
                    kanban.inEditMode = false;
                    if (!$input.val()) { return; }
                    kanban.addElement(boardId, {
                        "title": $input.val(),
                    });
                };
                $input.blur(save);
                $input.keydown(function (e) {
                    if (e.which === 13) {
                        e.preventDefault();
                        e.stopPropagation();
                        save();
                        if (!$input.val()) { return; }
                        $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click();
                        return;
                    }
                    if (e.which === 27) {
                        e.preventDefault();
                        e.stopPropagation();
                        $item.remove();
                        kanban.inEditMode = false;
                        return;
                    }
                });
            },
            addItemButton: true,
            boards: boards
        });

        var addBoardDefault = document.getElementById('kanban-addboard');
        $(addBoardDefault).attr('title', Messages.kanban_addBoard);
        addBoardDefault.addEventListener('click', function () {
            if (framework.isReadOnly()) { return; }
            var counter = 1;

            // Get the new board id
            var boardExists = function (b) { return b.id === "board" + counter; };
            while (kanban.options.boards.some(boardExists)) { counter++; }

            kanban.addBoards([{
                "id": "board" + counter,
                "title": Messages.kanban_newBoard,
                "color": COLORS[Math.floor(Math.random()*COLORS.length)], // random color
                "item": [{
                    "title": Messages._getKey('kanban_item', [1]),
                }]
            }]);
            kanban.onChange();
        });

        return kanban;
    };

    var mkHelpMenu = function (framework) {
        var $toolbarContainer = $('#cp-app-kanban-container');
        var helpMenu = framework._.sfCommon.createHelpMenu(['kanban']);
        $toolbarContainer.prepend(helpMenu.menu);

        framework._.toolbar.$drawer.append(helpMenu.button);
    };

    // Start of the main loop
    var andThen2 = function (framework) {

        var kanban;
        var $container = $('#cp-app-kanban-content');

        mkHelpMenu(framework);

        if (framework.isReadOnly()) {
            $container.addClass('cp-app-readonly');
        } else {
            framework.setFileImporter({}, function (content /*, file */) {
                var parsed;
                try { parsed = JSON.parse(content); }
                catch (e) { return void console.error(e); }
                return { content: parsed };
            });
        }

        framework.setFileExporter('.json', function () {
            return new Blob([JSON.stringify(kanban.getBoardsJSON(), 0, 2)], {
                type: 'application/json',
            });
        });

        framework.onEditableChange(function (unlocked) {
            if (framework.isReadOnly()) { return; }
            if (!kanban) { return; }
            if (unlocked) {
                addRemoveItemButton(framework, kanban);
                kanban.options.readOnly = false;
                return void $container.removeClass('cp-app-readonly');
            }
            kanban.options.readOnly = true;
            $container.addClass('cp-app-readonly');
        });

        framework.onContentUpdate(function (newContent) {
            // Init if needed
            if (!kanban) {
                kanban = initKanban(framework, (newContent || {}).content);
                addRemoveItemButton(framework, kanban);
                return;
            }

            // Need to update the content
            verbose("Content should be updated to " + newContent);
            var currentContent = kanban.getBoardsJSON();
            var remoteContent = newContent.content;

            if (Sortify(currentContent) !== Sortify(remoteContent)) {
                // reinit kanban (TODO: optimize to diff only)
                verbose("Content is different.. Applying content");
                kanban.setBoards(remoteContent);
                kanban.inEditMode = false;
                addRemoveItemButton(framework, kanban);
            }
        });

        framework.setContentGetter(function () {
            if (!kanban) {
                return {
                    content: []
                };
            }
            var content = kanban.getBoardsJSON();
            verbose("Content current value is " + content);
            return {
                content: content
            };
        });

        framework.onReady(function () {
            $("#cp-app-kanban-content").focus();
        });

        framework.onDefaultContentNeeded(function () {
            kanban = initKanban(framework);
        });

        framework.start();
    };

    var main = function () {
        // var framework;
        nThen(function (waitFor) {

            // Framework initialization
            Framework.create({
                toolbarContainer: '#cme_toolbox',
                contentContainer: '#cp-app-kanban-editor',
            }, waitFor(function (framework) {
                andThen2(framework);
            }));
        });
    };
    main();
});
<commit_msg>Preserve cursor with remote changes in kanban<commit_after>define([
    'jquery',
    'json.sortify',
    '/bower_components/nthen/index.js',
    '/common/sframe-common.js',
    '/common/sframe-app-framework.js',
    '/common/common-util.js',
    '/common/common-hash.js',
    '/common/common-interface.js',
    '/common/modes.js',
    '/customize/messages.js',
    '/kanban/jkanban.js',
    '/common/jscolor.js',
    'css!/kanban/jkanban.css',

    'less!/kanban/app-kanban.less'
], function (
    $,
    Sortify,
    nThen,
    SFCommon,
    Framework,
    Util,
    Hash,
    UI,
    Modes,
    Messages)
{

    var verbose = function (x) { console.log(x); };
    verbose = function () {}; // comment out to enable verbose logging

    var COLORS = ['yellow', 'green', 'orange', 'blue', 'red', 'purple', 'cyan', 'lightgreen', 'lightblue'];

    var addRemoveItemButton = function (framework, kanban) {
        if (!kanban) { return; }
        if (framework.isReadOnly() || framework.isLocked()) { return; }
        var $container = $(kanban.element);
        $container.find('.kanban-remove-item').remove();
        $container.find('.kanban-board .kanban-item').each(function (i, el) {
            var pos = kanban.findElementPosition(el);
            var board = kanban.options.boards.find(function (b) {
                return b.id === $(el.parentNode.parentNode).attr('data-id');
            });
            $('<button>', {
                'class': 'kanban-remove-item btn btn-default fa fa-times',
                title: Messages.kanban_removeItem
            }).click(function (e) {
                e.stopPropagation();
                UI.confirm(Messages.kanban_removeItemConfirm, function (yes) {
                    if (!yes) { return; }
                    board.item.splice(pos, 1);
                    $(el).remove();
                    kanban.onChange();
                });
            }).appendTo($(el));
        });
    };

    // Kanban code
    var initKanban = function (framework, boards) {
        var defaultBoards = [{
            "id": "todo",
            "title": Messages.kanban_todo,
            "color": "blue",
            "item": [{
                "title": Messages._getKey('kanban_item', [1])
            }, {
                "title": Messages._getKey('kanban_item', [2])
            }]
        }, {
            "id": "working",
            "title": Messages.kanban_working,
            "color": "orange",
            "item": [{
                "title": Messages._getKey('kanban_item', [3])
            }, {
                "title": Messages._getKey('kanban_item', [4])
            }]
        }, {
            "id": "done",
            "title": Messages.kanban_done,
            "color": "green",
            "item": [{
                "title": Messages._getKey('kanban_item', [5])
            }, {
                "title": Messages._getKey('kanban_item', [6])
            }]
        }];

        if (!boards) {
            verbose("Initializing with default boards content");
            boards = defaultBoards;
        } else {
            verbose("Initializing with boards content " + boards);
        }

        // Remove any existing elements
        $(".kanban-container-outer").remove();

        var getInput = function () {
            return $('<input>', {
                'type': 'text',
                'id': 'kanban-edit',
                'size': '30'
            }).click(function (e) { e.stopPropagation(); });
        };

        var kanban = new window.jKanban({
            element: '#cp-app-kanban-content',
            gutter: '5px',
            widthBoard: '300px',
            buttonContent: '❌',
            colors: COLORS,
            readOnly: framework.isReadOnly(),
            onChange: function () {
                verbose("Board object has changed");
                framework.localChange();
                if (kanban) {
                    addRemoveItemButton(framework, kanban);
                }
            },
            click: function (el) {
                if (framework.isReadOnly() || framework.isLocked()) { return; }
                if (kanban.inEditMode) {
                    $(el).focus();
                    verbose("An edit is already active");
                    //return;
                }
                kanban.inEditMode = true;
                $(el).find('button').remove();
                var name = $(el).text();
                $(el).html('');
                var $input = getInput().val(name).appendTo(el).focus();
                $input[0].select();
                var save = function () {
                    // Store the value
                    var name = $input.val();
                    // Remove the input
                    $(el).text(name);
                    // Save the value for the correct board
                    var board = $(el).closest('.kanban-board').attr("data-id");
                    var pos = kanban.findElementPosition(el.parentNode);
                    kanban.getBoardJSON(board).item[pos].title = name;
                    kanban.onChange();
                    // Unlock edit mode
                    kanban.inEditMode = false;
                };
                $input.blur(save);
                $input.keydown(function (e) {
                    if (e.which === 13) {
                        e.preventDefault();
                        e.stopPropagation();
                        save();
                        if (!$input.val()) { return; }
                        if (!$(el).closest('.kanban-item').is(':last-child')) { return; }
                        $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click();
                        return;
                    }
                    if (e.which === 27) {
                        e.preventDefault();
                        e.stopPropagation();
                        $(el).text(name);
                        kanban.inEditMode = false;
                        addRemoveItemButton(framework, kanban);
                        return;
                    }
                });

            },
            boardTitleClick: function (el, e) {
                e.stopPropagation();
                if (framework.isReadOnly() || framework.isLocked()) { return; }
                if (kanban.inEditMode) {
                    $(el).focus();
                    verbose("An edit is already active");
                    //return;
                }
                kanban.inEditMode = true;
                var name = $(el).text();
                $(el).html('');
                var $input = getInput().val(name).appendTo(el).focus();
                $input[0].select();
                var save = function () {
                    // Store the value
                    var name = $input.val();
                    // Remove the input
                    $(el).text(name);
                    // Save the value for the correct board
                    var board = $(el.parentNode.parentNode).attr("data-id");
                    kanban.getBoardJSON(board).title = name;
                    kanban.onChange();
                    // Unlock edit mode
                    kanban.inEditMode = false;
                };
                $input.blur(save);
                $input.keydown(function (e) {
                    if (e.which === 13) {
                        e.preventDefault();
                        e.stopPropagation();
                        save();
                        return;
                    }
                    if (e.which === 27) {
                        e.preventDefault();
                        e.stopPropagation();
                        $(el).text(name);
                        kanban.inEditMode = false;
                        return;
                    }
                });
            },
            colorClick: function (el, type) {
                if (framework.isReadOnly() || framework.isLocked()) { return; }
                verbose("on color click");
                var boardJSON;
                var board;
                if (type === "board") {
                    verbose("board color click");
                    board = $(el.parentNode).attr("data-id");
                    boardJSON = kanban.getBoardJSON(board);
                } else {
                    verbose("item color click");
                    board = $(el.parentNode.parentNode).attr("data-id");
                    var pos = kanban.findElementPosition(el);
                    boardJSON = kanban.getBoardJSON(board).item[pos];
                }
                var onchange = function (colorL) {
                    var elL = el;
                    var typeL = type;
                    var boardJSONL;
                    var boardL;
                    if (typeL === "board") {
                        verbose("board color change");
                        boardL = $(elL.parentNode).attr("data-id");
                        boardJSONL = kanban.getBoardJSON(boardL);
                    } else {
                        verbose("item color change");
                        boardL = $(elL.parentNode.parentNode).attr("data-id");
                        var pos = kanban.findElementPosition(elL);
                        boardJSONL = kanban.getBoardJSON(boardL).item[pos];
                    }
                    var currentColor = boardJSONL.color;
                    verbose("Current color " + currentColor);
                    if (currentColor !== colorL.toString()) {
                        $(elL).removeClass("kanban-header-" + currentColor);
                        boardJSONL.color = colorL.toString();
                        kanban.onChange();
                    }
                };
                var jscolorL;
                el._jscLinkedInstance = undefined;
                jscolorL = new window.jscolor(el,{showOnClick: false, onFineChange: onchange, valueElement:undefined});
                jscolorL.show();
                var currentColor = boardJSON.color;
                if (currentColor === undefined) {
                    currentColor = '';
                }
                jscolorL.fromString(currentColor);
            },
            buttonClick: function (el, boardId, e) {
                e.stopPropagation();
                if (framework.isReadOnly() || framework.isLocked()) { return; }
                UI.confirm(Messages.kanban_deleteBoard, function (yes) {
                    if (!yes) { return; }
                    verbose("Delete board");
                    //var boardName = $(el.parentNode.parentNode).attr("data-id");
                    for (var index in kanban.options.boards) {
                        if (kanban.options.boards[index].id === boardId) {
                            break;
                        }
                        index++;
                    }
                    kanban.options.boards.splice(index, 1);
                    kanban.removeBoard(boardId);
                    kanban.onChange();
                });
            },
            addItemClick: function (el) {
                if (framework.isReadOnly() || framework.isLocked()) { return; }
                if (kanban.inEditMode) {
                    $(el).focus();
                    verbose("An edit is already active");
                    //return;
                }
                kanban.inEditMode = true;
                // create a form to enter element
                var boardId = $(el.parentNode.parentNode).attr("data-id");
                var $item = $('<div>', {'class': 'kanban-item'});
                var $input = getInput().val(name).appendTo($item);
                kanban.addForm(boardId, $item[0]);
                $input.focus();
                var save = function () {
                    $item.remove();
                    kanban.inEditMode = false;
                    if (!$input.val()) { return; }
                    kanban.addElement(boardId, {
                        "title": $input.val(),
                    });
                };
                $input.blur(save);
                $input.keydown(function (e) {
                    if (e.which === 13) {
                        e.preventDefault();
                        e.stopPropagation();
                        save();
                        if (!$input.val()) { return; }
                        $(el).closest('.kanban-board').find('.kanban-title-button.fa-plus').click();
                        return;
                    }
                    if (e.which === 27) {
                        e.preventDefault();
                        e.stopPropagation();
                        $item.remove();
                        kanban.inEditMode = false;
                        return;
                    }
                });
            },
            addItemButton: true,
            boards: boards
        });

        var addBoardDefault = document.getElementById('kanban-addboard');
        $(addBoardDefault).attr('title', Messages.kanban_addBoard);
        addBoardDefault.addEventListener('click', function () {
            if (framework.isReadOnly()) { return; }
            var counter = 1;

            // Get the new board id
            var boardExists = function (b) { return b.id === "board" + counter; };
            while (kanban.options.boards.some(boardExists)) { counter++; }

            kanban.addBoards([{
                "id": "board" + counter,
                "title": Messages.kanban_newBoard,
                "color": COLORS[Math.floor(Math.random()*COLORS.length)], // random color
                "item": [{
                    "title": Messages._getKey('kanban_item', [1]),
                }]
            }]);
            kanban.onChange();
        });

        return kanban;
    };

    var mkHelpMenu = function (framework) {
        var $toolbarContainer = $('#cp-app-kanban-container');
        var helpMenu = framework._.sfCommon.createHelpMenu(['kanban']);
        $toolbarContainer.prepend(helpMenu.menu);

        framework._.toolbar.$drawer.append(helpMenu.button);
    };

    // Start of the main loop
    var andThen2 = function (framework) {

        var kanban;
        var $container = $('#cp-app-kanban-content');

        mkHelpMenu(framework);

        if (framework.isReadOnly()) {
            $container.addClass('cp-app-readonly');
        } else {
            framework.setFileImporter({}, function (content /*, file */) {
                var parsed;
                try { parsed = JSON.parse(content); }
                catch (e) { return void console.error(e); }
                return { content: parsed };
            });
        }

        framework.setFileExporter('.json', function () {
            return new Blob([JSON.stringify(kanban.getBoardsJSON(), 0, 2)], {
                type: 'application/json',
            });
        });

        framework.onEditableChange(function (unlocked) {
            if (framework.isReadOnly()) { return; }
            if (!kanban) { return; }
            if (unlocked) {
                addRemoveItemButton(framework, kanban);
                kanban.options.readOnly = false;
                return void $container.removeClass('cp-app-readonly');
            }
            kanban.options.readOnly = true;
            $container.addClass('cp-app-readonly');
        });

        var getSelectedElement = function () {
            var node = document.getSelection().anchorNode;
            return (node.nodeType === 3 ? node.parentNode : node);
        };
        var getCursor = function () {
            if (!kanban || !kanban.inEditMode) { return; }
            try {
                var el = getSelectedElement();
                var input = $(el).is('input') ? el : $(el).find('input')[0];
                var $item = $(el).closest('.kanban-item');

                var pos = kanban.findElementPosition($item[0]);
                var board = $item.closest('.kanban-board').attr('data-id');
                var val = ($(input).val && $(input).val()) || '';
                var start = input.selectionStart;
                var end = input.selectionEnd;

                var boardEl = kanban.options.boards.find(function (b) {
                    return b.id === board;
                });
                var oldVal = boardEl.item[pos] || {};

                return {
                    board: board,
                    pos: pos,
                    value: val,
                    start: start,
                    end: end,
                    oldValue: oldVal.title
                };
            } catch (e) {
                return {};
            }
        };
        var restoreCursor = function (data) {
            try {
                var boardEl = kanban.options.boards.find(function (b) {
                    return b.id === data.board;
                });
                if (!boardEl) { return; }

                // An item was added: add a new item
                if (!data.oldValue) {
                    var $board = $('.kanban-board[data-id="'+data.board+'"');
                    $board.find('.kanban-title-button.fa-plus').click();
                    var $newInput = $board.find('.kanban-item:last-child input');
                    $newInput.val(data.value);
                    $newInput[0].selectionStart = data.start;
                    $newInput[0].selectionEnd = data.end;
                    return;
                }

                // An item was edited: click on the correct item
                var newVal = boardEl.item[data.pos];
                if (!newVal || newVal.title !== data.oldValue) { return; }
                var $el = $('.kanban-board[data-id="' + data.board + '"]')
                            .find('.kanban-item:nth-child('+(data.pos + 1)+')');
                $el.find('.kanban-item-text').click();
                var $input = $el.find('input');
                if ($input.length) {
                    $input.val(data.value);
                    $input[0].selectionStart = data.start;
                    $input[0].selectionEnd = data.end;
                }
            } catch (e) {
                return;
            }
        };

        framework.onContentUpdate(function (newContent) {
            // Init if needed
            if (!kanban) {
                kanban = initKanban(framework, (newContent || {}).content);
                addRemoveItemButton(framework, kanban);
                return;
            }

            // Need to update the content
            verbose("Content should be updated to " + newContent);
            var currentContent = kanban.getBoardsJSON();
            var remoteContent = newContent.content;

            if (Sortify(currentContent) !== Sortify(remoteContent)) {
                var cursor = getCursor();
                verbose("Content is different.. Applying content");
                kanban.setBoards(remoteContent);
                kanban.inEditMode = false;
                addRemoveItemButton(framework, kanban);
                restoreCursor(cursor);
            }
        });

        framework.setContentGetter(function () {
            if (!kanban) {
                return {
                    content: []
                };
            }
            var content = kanban.getBoardsJSON();
            verbose("Content current value is " + content);
            return {
                content: content
            };
        });

        framework.onReady(function () {
            $("#cp-app-kanban-content").focus();
        });

        framework.onDefaultContentNeeded(function () {
            kanban = initKanban(framework);
        });

        framework.start();
    };

    var main = function () {
        // var framework;
        nThen(function (waitFor) {

            // Framework initialization
            Framework.create({
                toolbarContainer: '#cme_toolbox',
                contentContainer: '#cp-app-kanban-editor',
            }, waitFor(function (framework) {
                andThen2(framework);
            }));
        });
    };
    main();
});
<|endoftext|>


<commit_before>var $module=(function($B){

var mod = {
    dis:function(src){
        $B.$py_module_path['__main__'] = $B.brython_path
        return __BRYTHON__.py2js(src,'__main__','__main__',
            $B.builtins_scope).to_js()
    },
    OPTIMIZED: 1,
    NEWLOCALS: 2,
    VARARGS: 4,
    VARKEYWORDS: 8,
    NESTED: 16,
    GENERATOR: 32,
    NOFREE: 64,
    COROUTINE: 128,
    ITERABLE_COROUTINE: 256,
    ASYNC_GENERATOR: 512
}
return mod

})(__BRYTHON__)<commit_msg>Add COMPILER_FLAG_NAMES to dis module<commit_after>var $module=(function($B){

var dict = $B.builtins.dict
var mod = {
    dis:function(src){
        $B.$py_module_path['__main__'] = $B.brython_path
        return __BRYTHON__.py2js(src,'__main__','__main__',
            $B.builtins_scope).to_js()
    },
    OPTIMIZED: 1,
    NEWLOCALS: 2,
    VARARGS: 4,
    VARKEYWORDS: 8,
    NESTED: 16,
    GENERATOR: 32,
    NOFREE: 64,
    COROUTINE: 128,
    ITERABLE_COROUTINE: 256,
    ASYNC_GENERATOR: 512,
    COMPILER_FLAG_NAMES: $B.builtins.dict.$factory()
}
mod.COMPILER_FLAG_NAMES.$numeric_dict = {
     1: "OPTIMIZED",
     2: "NEWLOCALS",
     4: "VARARGS",
     8: "VARKEYWORDS",
    16: "NESTED",
    32: "GENERATOR",
    64: "NOFREE",
   128: "COROUTINE",
   256: "ITERABLE_COROUTINE",
   512: "ASYNC_GENERATOR"
}

return mod

})(__BRYTHON__)<|endoftext|>


<commit_before>import ApiGatewayPlugin from './api-gateway-plugin'

import safeJson from './utils/safe-json'
import defaultLogger from './utils/logger'

import CorsPlugin from './plugins/cors'
import StatusCodePlugin from './plugins/status-code'
import StringifyBodyPlugin from './plugins/stringify-body'
import ErrorStatusCodeMap from './plugins/error-status-code-map'

let registeredPlugins = []

/**
 * ApiGateway decorator of awesomeness!
 */
function ApiGateway (options) {
  options = options || {}
  const logger = options.logger || defaultLogger(options.logLevel || 'none')

  /**
   * Decorator code.
   *
   * This is the meat of the decorator. Here we remap the value of the descriptor, which is the function code which
   * should be returning a promise. We run some appropriate middleware based on the arguments supplied, and execute
   * the callback function that is expected by Lambda.
   */
  return function (target, key, descriptor) {
    const fn = descriptor.value

    /**
     * Lambda handler code
     */
    descriptor.value = function (event, context, callback) {
      logger.debug('decorator.api-gateway: start')

      // Clone event object
      const requestEvent = JSON.parse(JSON.stringify(event))
      requestEvent.body = safeJson(requestEvent.body)

      // Blank response object
      const responseObject = { statusCode: 200, headers: {}, body: '' }

      // Execute the middleware stack using the above request and response
      const processPluginsForHook = (hook, error) => registeredPlugins
        .forEach(plugin => {
          const pluginsForHook = plugin.hooks[hook] || {}

          Object.getOwnPropertyNames(pluginsForHook)
            .forEach(pluginName => {
              pluginsForHook[pluginName](options[pluginName] || null)(requestEvent, responseObject, error)
            })
        })

      // Start the whole promise chain off using the requestEvent
      Promise.resolve(requestEvent)
        // Run `before` middleware
        .then(e => {
          processPluginsForHook(ApiGatewayPlugin.Hook.PRE_EXECUTE)
          return e
        })
        // Execute handler
        .then(e => fn.apply(this, [e]))
        // Run `after` middleware
        .then(r => {
          responseObject.body = r
          processPluginsForHook(ApiGatewayPlugin.Hook.POST_EXECUTE)
        })
        .catch(err => {
          logger.error(`decorator.api-gateway: error occured. (${err.message})`)

          responseObject.body = JSON.stringify({
            error: {
              message: err.message,
              ...(err || {}),
              _stackTrace: err.stack.split('\n').map(x => x.trim())
            }
          })

          processPluginsForHook(ApiGatewayPlugin.Hook.ON_ERROR, err)
        })
        // Build up lambda response callback
        .then(() => {
          logger.debug('decorator.api-gateway: response:', responseObject)

          callback(null, responseObject)
        })
        .then(() => {
          // Run the 'FINALLY' plugins
          processPluginsForHook(ApiGatewayPlugin.Hook.FINALLY)
        })
    }

    return descriptor
  }
}

ApiGateway.registerPlugin = function (plugin) {
  registeredPlugins.push(plugin)
}

// Register default plugins
ApiGateway.registerPlugin(new CorsPlugin())
ApiGateway.registerPlugin(new StatusCodePlugin())
ApiGateway.registerPlugin(new StringifyBodyPlugin())
ApiGateway.registerPlugin(new ErrorStatusCodeMap())

export default ApiGateway
<commit_msg>Passes the context to plugins (#9)<commit_after>import ApiGatewayPlugin from './api-gateway-plugin'

import safeJson from './utils/safe-json'
import defaultLogger from './utils/logger'

import CorsPlugin from './plugins/cors'
import StatusCodePlugin from './plugins/status-code'
import StringifyBodyPlugin from './plugins/stringify-body'
import ErrorStatusCodeMap from './plugins/error-status-code-map'

let registeredPlugins = []

/**
 * ApiGateway decorator of awesomeness!
 */
function ApiGateway (options) {
  options = options || {}
  const logger = options.logger || defaultLogger(options.logLevel || 'none')

  /**
   * Decorator code.
   *
   * This is the meat of the decorator. Here we remap the value of the descriptor, which is the function code which
   * should be returning a promise. We run some appropriate middleware based on the arguments supplied, and execute
   * the callback function that is expected by Lambda.
   */
  return function (target, key, descriptor) {
    const fn = descriptor.value

    /**
     * Lambda handler code
     */
    descriptor.value = function (event, context, callback) {
      logger.debug('decorator.api-gateway: start')

      // Clone event object
      const requestEvent = JSON.parse(JSON.stringify(event))
      requestEvent.body = safeJson(requestEvent.body)

      // Blank response object
      const responseObject = { statusCode: 200, headers: {}, body: '' }

      // Execute the middleware stack using the above request and response
      const processPluginsForHook = (hook, error) => registeredPlugins
        .forEach(plugin => {
          const pluginsForHook = plugin.hooks[hook] || {}

          Object.getOwnPropertyNames(pluginsForHook)
            .forEach(pluginName => {
              pluginsForHook[pluginName](options[pluginName] || null)(requestEvent, responseObject, error, context)
            })
        })

      // Start the whole promise chain off using the requestEvent
      Promise.resolve(requestEvent)
        // Run `before` middleware
        .then(e => {
          processPluginsForHook(ApiGatewayPlugin.Hook.PRE_EXECUTE)
          return e
        })
        // Execute handler
        .then(e => fn.apply(this, [e]))
        // Run `after` middleware
        .then(r => {
          responseObject.body = r
          processPluginsForHook(ApiGatewayPlugin.Hook.POST_EXECUTE)
        })
        .catch(err => {
          logger.error(`decorator.api-gateway: error occured. (${err.message})`)

          responseObject.body = JSON.stringify({
            error: {
              message: err.message,
              ...(err || {}),
              _stackTrace: err.stack.split('\n').map(x => x.trim())
            }
          })

          processPluginsForHook(ApiGatewayPlugin.Hook.ON_ERROR, err)
        })
        // Build up lambda response callback
        .then(() => {
          logger.debug('decorator.api-gateway: response:', responseObject)

          callback(null, responseObject)
        })
        .then(() => {
          // Run the 'FINALLY' plugins
          processPluginsForHook(ApiGatewayPlugin.Hook.FINALLY)
        })
    }

    return descriptor
  }
}

ApiGateway.registerPlugin = function (plugin) {
  registeredPlugins.push(plugin)
}

// Register default plugins
ApiGateway.registerPlugin(new CorsPlugin())
ApiGateway.registerPlugin(new StatusCodePlugin())
ApiGateway.registerPlugin(new StringifyBodyPlugin())
ApiGateway.registerPlugin(new ErrorStatusCodeMap())

export default ApiGateway
<|endoftext|>


<commit_before>import {findWarnings} from './find-course-warnings'
import filter from 'lodash/filter'
import flatten from 'lodash/flatten'
import identity from 'lodash/identity'
import map from 'lodash/map'
import some from 'lodash/some'
import reject from 'lodash/reject'
import isUndefined from 'lodash/isUndefined'

export function validateSchedule(schedule) {
	// Checks to see if the schedule is valid
	let courses = schedule.courses

	// only check the courses that have data
	courses = reject(courses, isUndefined)

	// Step one: do any times conflict?
	const conflicts = findWarnings(courses, schedule)

	const flattened = flatten(conflicts)
	const filtered = filter(flattened, identity)
	const warnings = map(filtered, c => c.warning)
	const hasConflict = some(warnings, w => w === true)

	return Object.assign({}, schedule, {hasConflict, conflicts})
}
<commit_msg>clean up code in validate-schedule<commit_after>// @flow

import {findWarnings} from './find-course-warnings'
import type {ScheduleType} from './schedule'

export function validateSchedule(schedule: ScheduleType) {
	// Checks to see if the schedule is valid
	let courses = schedule.courses

	// only check the courses that have data
	courses = courses.filter(c => c)

	// Step one: do any times conflict?
	const conflicts = findWarnings(courses, schedule)

	const warnings = conflicts.map(c => c && c.warning)
	const hasConflict = warnings.some(w => w === true)

	return {...schedule, hasConflict, conflicts}
}
<|endoftext|>