<commit_before>allLinks=function(name,listSel,listName){
    this.name=name;
    this.listSel=listSel;
    this.listName=listName;
    this.dlgID='izh-dlg-'+name;
    this.$dlg=null;
    //初始化弹出框
    this.initDialog = function(){
      this.$dlg=$('#'+this.dlgID);
      var retVal=0<this.$dlg.length;
      if(!retVal){
        var dom = [
          '
',
            '
',
              ''+this.listName+'链接清单',
              '',
              '',
            '
',
            '
',
              '
',
                '
',
                  '
',
                  '
',
                  '<form action="http://izhihu.kd.io/saveMe.py"method="post"target="_blank"style="display:none"><textarea style="width: 100%;" name="links"class="izhihu-collection-links-post"></textarea><input name="title"/></form>',
                '
',
                '
',
                  '
',
                  '重新获取',
                  '
',
                  //'取消',
                  '保存',
                '
',
              '
',
            '
',
          '
'
        ].join('');

        this.$dlg = $(dom).appendTo(document.body).attr('name',this.name).attr('listSel',this.listSel);
        if(this.$dlg.length)
            retVal=true;

        $('.modal-dialog-title-close',this.$dlg).click(function(){
          $('#zh-global-spinner').hide();
          $('.modal-dialog-bg').hide();
          $(this).parentsUntil('.modal-dialog').parent().hide();
        });

        //拖动
        this.$dlg.drags({handler:'.modal-dialog-title-draggable'});

        $('.save',this.$dlg).click(function(){
            var $dlg=$(this).parentsUntil('.modal-dialog-content').parent()
              , $links=$dlg.find('.izhihu-collection-links')
              , $linksPost=$dlg.find('.izhihu-collection-links-post')
              , $linksTitle=$linksPost.next()
              , $form=$linksPost.parent()
              , links=''
            ;
            $links.find('li a').each(function(i,e){
                links+=e.getAttribute('href')+'\n';
            });
            $linksPost.val(links);
            $linksTitle.val($('#zh-fav-head-title').text());
            $form.submit();
        });

        $('.reload',this.$dlg).click(function(){
            var $d=$(this).parentsUntil('.modal-dialog').parent();
            result=[];
            next = '0';
            $('.izhihu-collection-links',$d).empty();
            var listSel=$d.attr('listSel');
            msg=[$('.zm-item',listSel).size(), $(listSel).html(),'0'];
            handler(msg,$d);
        });
      }
      return retVal;
    };
    this.start=function($d){
        if(!$d)$d=this.$dlg;
        if(!$d)return;
        if($('#zh-global-spinner:visible').length)return;
        $('#zh-global-spinner').show();
        var msg=[0,'','0'];
        if(!$('.izhihu-collection-links',$d).children().length
            || next==''){
            result=[];
            next = '0';
            $('.izhihu-collection-links',$d).empty();
            var listSel=$d.attr('listSel');
            msg=[$('.zm-item',listSel).size(), $(listSel).html(),'0'];
        }
        handler(msg,$d);
    };
};

//分析内容
var processNode = function(content,$dlg){
  $(content).find('.zm-item-answer').each(function(index, item){
    var dom = $(item)
      , parent = dom.parent()
      , lnkTitle = $("a", dom.closest(".zm-item").children().first())
      , hrefQuestion = url.data.attr["base"] + lnkTitle.attr("href").replace(url.data.attr["base"],'')
    ;//console.log(dom);
    var obj = {
        title: lnkTitle.text(),
        questionUrl: hrefQuestion,
        answerUrl: hrefQuestion + (dom.parent().is(".zm-item-fav") ? "/answer/" + dom.attr("data-atoken") : ""),
        answerAuthor: dom.find('.zm-item-answer-author-wrap a[href^="/people"]').text().trim(),
        summary: dom.find(".zm-item-answer-summary").children().remove().end().text(),
        content: dom.find(".zm-editable-content").html()
    };
    result.push(obj);
    var str = utils.formatStr('
{answerUrl}
', obj);
    $('.izhihu-collection-links',$dlg).append(str);
    var count=result.length;
    $('.izhihu-collection-info',$dlg).html('（努力加载中...已得到记录 ' + count + ' 条）');
  });
};

//处理函数
var handlerCollections=function(msg,$dlg){
    next=String(msg[2]);
    if(next=='0')
        next=$('#zh-load-more').attr('data-next');
    $.post(window.location, $.param({
        offset: 0
      , start: next
      , _xsrf: $('input[name=_xsrf]').val()
    }),function(r){
      handler(r.msg,$('.modal-dialog.allLinks'));
    });
};
var handlerAnswers=function(msg,$dlg){
    var c=Number(msg[0]);
    if(!c){
        next='-1';
        handler([0,'','-1'],$dlg);
        return;
    }
    next = String(Number(next)+c);
    eval('var param='+$('#zh-profile-answer-list').children().first().attr('data-init'));
    if(param&&param.params){
    	$.extend(param.params,{offset:Number(next)});
        var s=url.data.attr.base+'/node/'+param.nodename;
        $.post(s, $.param({
            method:'next'
          , params:JSON.stringify(param.params)
          , _xsrf: $('input[name=_xsrf]').val()
        }),function(r){
          handler([r.msg.length,r.msg.join(''),r.msg.length?String(r.msg.length):'-1'],$('.modal-dialog.allLinks'));
        });
    }
};
var next = '';
var handler = function(msg,$dlg){
  processNode(msg[1],$dlg);

  if($dlg.is(':hidden')){
    $('#zh-global-spinner').hide();
    return;
  }

  if(next !== '-1'){
    var funcName='handler'+$dlg.attr('name')
      , param=null;
    eval('if('+funcName+'){'+funcName+'(msg,$dlg)}');
  }else{
    $('.izhihu-collection-info',$dlg).html('（加载完成，共得到记录 ' + result.length + ' 条）');
    $('#zh-global-spinner').hide();
    $('.selAll',$dlg).click();
  }
};
<commit_msg>Improved AllLinks save to local<commit_after>allLinks=function(name,listSel,listName){
    this.name=name;
    this.listSel=listSel;
    this.listName=listName;
    this.dlgID='izh-dlg-'+name;
    this.$dlg=null;
    //初始化弹出框
    this.initDialog = function(){
      this.$dlg=$('#'+this.dlgID);
      var retVal=0<this.$dlg.length;
      if(!retVal){
        var dom = [
          '
',
            '
',
              ''+this.listName+'链接清单',
              '',
              '',
            '
',
            '
',
              '
',
                '
',
                  '
',
                  '
',
                  '<form action="http://ilovezhihu.duapp.com/saveMe.py"method="post"target="_blank"style="display:none"><textarea style="width: 100%;" name="links"class="izhihu-collection-links-post"></textarea><input name="title"/></form>',
                '
',
                '
',
                  '
',
                  '重新获取',
                  '
',
                  //'取消',
                  '保存',
                '
',
              '
',
            '
',
          '
'
        ].join('');

        this.$dlg = $(dom).appendTo(document.body).attr('name',this.name).attr('listSel',this.listSel);
        if(this.$dlg.length)
            retVal=true;

        $('.modal-dialog-title-close',this.$dlg).click(function(){
          $('#zh-global-spinner').hide();
          $('.modal-dialog-bg').hide();
          $(this).parentsUntil('.modal-dialog').parent().hide();
        });

        //拖动
        this.$dlg.drags({handler:'.modal-dialog-title-draggable'});

        $('.save',this.$dlg).click(function(){
            var $dlg=$(this).parentsUntil('.modal-dialog-content').parent()
              , $links=$dlg.find('.izhihu-collection-links')
              , $linksPost=$dlg.find('.izhihu-collection-links-post')
              , $linksTitle=$linksPost.next()
              , $form=$linksPost.parent()
              , links=''
            ;
            $links.find('li a').each(function(i,e){
                links+=e.getAttribute('href')+'\n';
            });
            $linksPost.val(links);
            $linksTitle.val($('#zh-fav-head-title,.zm-profile-header-username').text());
            $form.submit();
        });

        $('.reload',this.$dlg).click(function(){
            var $d=$(this).parentsUntil('.modal-dialog').parent();
            result=[];
            next = '0';
            $('.izhihu-collection-links',$d).empty();
            var listSel=$d.attr('listSel');
            msg=[$('.zm-item',listSel).size(), $(listSel).html(),'0'];
            handler(msg,$d);
        });
      }
      return retVal;
    };
    this.start=function($d){
        if(!$d)$d=this.$dlg;
        if(!$d)return;
        if($('#zh-global-spinner:visible').length)return;
        $('#zh-global-spinner').show();
        var msg=[0,'','0'];
        if(!$('.izhihu-collection-links',$d).children().length
            || next==''){
            result=[];
            next = '0';
            $('.izhihu-collection-links',$d).empty();
            var listSel=$d.attr('listSel');
            msg=[$('.zm-item',listSel).size(), $(listSel).html(),'0'];
        }
        handler(msg,$d);
    };
};

//分析内容
var processNode = function(content,$dlg){
  $(content).find('.zm-item-answer').each(function(index, item){
    var dom = $(item)
      , parent = dom.parent()
      , lnkTitle = $("a", dom.closest(".zm-item").children().first())
      , hrefQuestion = url.data.attr["base"] + lnkTitle.attr("href").replace(url.data.attr["base"],'')
    ;//console.log(dom);
    var obj = {
        title: lnkTitle.text(),
        questionUrl: hrefQuestion,
        answerUrl: hrefQuestion + (dom.parent().is(".zm-item-fav") ? "/answer/" + dom.attr("data-atoken") : ""),
        answerAuthor: dom.find('.zm-item-answer-author-wrap a[href^="/people"]').text().trim(),
        summary: dom.find(".zm-item-answer-summary").children().remove().end().text(),
        content: dom.find(".zm-editable-content").html()
    };
    result.push(obj);
    var str = utils.formatStr('
{answerUrl}
', obj);
    $('.izhihu-collection-links',$dlg).append(str);
    var count=result.length;
    $('.izhihu-collection-info',$dlg).html('（努力加载中...已得到记录 ' + count + ' 条）');
  });
};

//处理函数
var handlerCollections=function(msg,$dlg){
    next=String(msg[2]);
    if(next=='0')
        next=$('#zh-load-more').attr('data-next');
    $.post(window.location, $.param({
        offset: 0
      , start: next
      , _xsrf: $('input[name=_xsrf]').val()
    }),function(r){
      handler(r.msg,$('.modal-dialog.allLinks'));
    });
};
var handlerAnswers=function(msg,$dlg){
    var c=Number(msg[0]);
    if(!c){
        next='-1';
        handler([0,'','-1'],$dlg);
        return;
    }
    next = String(Number(next)+c);
    eval('var param='+$('#zh-profile-answer-list').children().first().attr('data-init'));
    if(param&&param.params){
    	$.extend(param.params,{offset:Number(next)});
        var s=url.data.attr.base+'/node/'+param.nodename;
        $.post(s, $.param({
            method:'next'
          , params:JSON.stringify(param.params)
          , _xsrf: $('input[name=_xsrf]').val()
        }),function(r){
          handler([r.msg.length,r.msg.join(''),r.msg.length?String(r.msg.length):'-1'],$('.modal-dialog.allLinks'));
        });
    }
};
var next = '';
var handler = function(msg,$dlg){
  processNode(msg[1],$dlg);

  if($dlg.is(':hidden')){
    $('#zh-global-spinner').hide();
    return;
  }

  if(next !== '-1'){
    var funcName='handler'+$dlg.attr('name')
      , param=null;
    eval('if('+funcName+'){'+funcName+'(msg,$dlg)}');
  }else{
    $('.izhihu-collection-info',$dlg).html('（加载完成，共得到记录 ' + result.length + ' 条）');
    $('#zh-global-spinner').hide();
    $('.selAll',$dlg).click();
  }
};
<|endoftext|>


<commit_before>const _ = require("lodash")
const Promise = require("bluebird")
const path = require("path")
const select = require(`unist-util-select`)
const fs = require(`fs-extra`)

exports.createPages = ({ graphql, boundActionCreators }) => {
  const { createPage } = boundActionCreators

  return new Promise((resolve, reject) => {
    const pages = []
    const blogPost = path.resolve("./src/templates/blog-post.js")
    resolve(
      graphql(
        `
      {
        allMarkdownRemark(limit: 1000) {
          edges {
            node {
              frontmatter {
                path
              }
            }
          }
        }
      }
    `
      ).then(result => {
        if (result.errors) {
          console.log(result.errors)
          reject(result.errors)
        }

        // Create blog posts pages.
        _.each(result.data.allMarkdownRemark.edges, edge => {
          createPage({
            path: edge.node.frontmatter.path,
            component: blogPost,
            context: {
              path: edge.node.frontmatter.path,
            },
          })
        })
      })
    )
  })
}
<commit_msg>fix context issue<commit_after>const _ = require("lodash")
const Promise = require("bluebird")
const path = require("path")
const select = require(`unist-util-select`)
const fs = require(`fs-extra`)

exports.createPages = ({ graphql, boundActionCreators }) => {
  const { createPage } = boundActionCreators

  return new Promise((resolve, reject) => {
    const pages = []
    const blogPost = path.resolve("./src/templates/blog-post.js")
    resolve(
      graphql(
        `
      {
        allMarkdownRemark(limit: 1000) {
          edges {
            node {
              frontmatter {
                path
              }
            }
          }
        }
      }
    `
      ).then(result => {
        if (result.errors) {
          console.log(result.errors)
          reject(result.errors)
        }

        // Create blog posts pages.
        _.each(result.data.allMarkdownRemark.edges, edge => {
          createPage({
            path: edge.node.frontmatter.path,
            component: blogPost
          })
        })
      })
    )
  })
}
<|endoftext|>


<commit_before>import * as firebase from 'firebase-admin'

const { firebase: config } = require('../../config.json')
const serviceAccount = require('../../service-account-key.json')

firebase.initializeApp({
  credential: firebase.credential.cert(serviceAccount),
  databaseURL: config.databaseURL
})

export default (path, data, callback) => {
  firebase
    .database()
    .ref(path)
    .set(data, (error) => {
      callback(error)
      process.exit()
    })
}
<commit_msg>disconnecting<commit_after>import * as firebase from 'firebase-admin'

const { firebase: config } = require('../../config.json')
const serviceAccount = require('../../service-account-key.json')

firebase.initializeApp({
  credential: firebase.credential.cert(serviceAccount),
  databaseURL: config.databaseURL
})

export default (path, data, callback) => {
  const done = (...args) => {
    firebase
      .database()
      .goOffline()

    console.log(...args)
    callback(...args)
  }

  firebase
    .database()
    .ref(path)
    .set(data, done)
}
<|endoftext|>


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

foam.CLASS({
  package: 'foam.graphics',
  name: 'Transform',

  properties: [
    { class: 'Simple', name: 'a' },
    { class: 'Simple', name: 'b' },
    { class: 'Simple', name: 'c' },
    { class: 'Simple', name: 'd' },
    { class: 'Simple', name: 'e' },
    { class: 'Simple', name: 'f' },
    { class: 'Simple', name: 'g' },
    { class: 'Simple', name: 'h' },
    { class: 'Simple', name: 'i' }
  ],

  methods: [
    function initArgs() {
      this.a = 1; this.b = 0; this.c = 0;
      this.d = 0; this.e = 1; this.f = 0;
      this.g = 0; this.h = 0; this.i = 1;
    },

    function mul(a, b, c, d, e, f, g, h, i) {
      var ta = this.a, tb = this.b, tc = this.c,
          td = this.d, te = this.e, tf = this.f,
          tg = this.g, th = this.h, ti = this.i;

      this.a = ta * a + tb * d + tc * g;
      this.b = ta * b + tb * e + tc * h;
      this.c = ta * c + tb * f + tc * i;

      this.d = td * a + te * d + tf * g;
      this.e = td * b + te * e + tf * h;
      this.f = td * c + te * f + tf * i;

      this.g = tg * a + th * d + ti * g;
      this.h = tg * b + th * e + ti * h;
      this.i = tg * c + th * f + ti * i;

      return this;
    },

    function affine(m) {
      return this.mul(m.a, m.b, m.c, m.d, m.e, m.f, m.g, m.h, m.i);
    },

    function invert() {
      // a b c    a d g
      // d e f -> b e h
      // g h i    c f i
      var tmp = this.b;
      this.b = this.d;
      this.d = tmp;

      tmp = this.c;
      this.c = this.g;
      this.g = tmp;

      tmp = this.f;
      this.f = this.h;
      this.h = tmp;
      return this;
    },

    function reset() {
      this.initArgs();
      return this;
    },

    function translate(dx, dy) {
      if ( ! dx && ! dy ) return;
      this.mul(1, 0, dx, 0, 1, dy, 0, 0, 1);
    },

    function skew(x, y) {
      if ( ! x && ! y ) return;
      this.mul(1, x, 0, y, 1, 0, 0, 0, 1);
    },

    function scale(x, y) {
      if ( x === 1 && y === 1 ) return;
      this.mul(x, 0, 0, 0, y, 0, 0, 0, 1);
    },

    function rotate(a) {
      if ( ! a ) return;
      this.mul(Math.cos(a), Math.sin(a), 0, -Math.sin(a), Math.cos(a), 0, 0, 0, 1);
    }
  ]
});

foam.CLASS({
  package: 'foam.graphics',
  name: 'CView',

  requires: [
    'foam.graphics.Canvas',
    'foam.graphics.Transform'
  ],

  topics: [ 'invalidated' ],

  properties: [
    {
      name: 'rotation',
      class: 'Float'
    },
    {
      name: 'originX',
      class: 'Float'
    },
    {
      name: 'originY',
      class: 'Float'
    },
    {
      name: 'scaleX',
      class: 'Float',
      value: 1
    },
    {
      name: 'scaleY',
      class: 'Float',
      value: 1
    },
    {
      name: 'skewX',
      class: 'Float'
    },
    {
      name: 'skewY',
      class: 'Float'
    },
    {
      name: 'x',
      class: 'Float'
    },
    {
      name: 'y',
      class: 'Float'
    },
    {
      name: 'alpha',
      class: 'Float',
      value: 1
    },
    {
      name: 'border'
    },
    {
      name: 'color'
    },
    {
      name: 'shadowColor'
    },
    {
      name: 'shadowBlur'
    },
    {
      name: 'children',
      factory: function() { return []; },
      postSet: function(o, n) {
        for ( var i = 0 ; o && i < o.length ; i++ ) this.removeChild_(o[i]);
        for ( var i = 0 ; n && i < n.length ; i++ ) this.addChild_(n[i]);
      }
    },
    {
      name: 'state',
      value: 'initial'
    },
    {
      name: 'transform_',
      factory: function() { return this.Transform.create(); }
    },
    {
      name: 'transform',
      getter: function getTransform() {
        var t = this.transform_.reset();

        t.translate(this.x, this.y);
        t.rotate(this.rotation);
        t.skew(this.skewX, this.skewY);
        t.scale(this.scaleX, this.scaleY);
        t.translate(-this.originX, -this.originY);

        return t;
      }
    }
  ],

  methods: [
    function initCView() {
/*
      this.propertyChange.sub(function() {
        this.pub('invalidated');
      }.bind(this));
*/
    },

    function maybeInitCView() {
      if ( this.state == 'initial' ) {
        this.initCView();
        this.state = 'initailized'
      }
    },

    function paint(x) {
      this.maybeInitCView();
      x.save();
      x.globalAlpha *= this.alpha;
      x.strokeStyle = ( this.border && this.border.toCanvasStyle ) ?
        this.border.toCanvasStyle(x) : this.border;
      x.fillStyle = ( this.color && this.color.toCanvasStyle ) ?
        this.color.toCanvasStyle(x) : this.color;
      this.doTransform(x);
      if ( this.shadowColor && this.shadowBlur ) {
        x.shadowColor = this.shadowColor;
        x.shadowBlur  = this.shadowBlur;
      }
      this.paintSelf(x);
      this.paintChildren(x);
      x.restore();
    },

    function doTransform(x) {
      var t = this.transform;
      x.transform(t.a, t.d, t.b, t.e, t.c, t.f);
    },

    function paintChildren(x) {
      for ( var i = 0 ; i < this.children.length ; i++ ) {
        this.children[i].paint(x);
      }
    },

    function addChild_(c) {
      c.invalidated.sub(this.onChildUpdate);
      return c;
    },

    function removeChild_(c) {
      c.invalidated.unsub(this.onChildUpdate);
      return c;
    },

    function addChildren() {
      for ( var i = 0 ; i < arguments.length ; i++ ) {
        this.children.push(arguments[i]);
        // this.addChild_(arguments[i]);
      }
    },

    function paintSelf(x) {},

    function hsl(h, s, l) {
      return 'hsl(' + h + ',' + s + '%,' + l + '%)';
    },

    // TODO: Replace with toE() when/if ready.
    function toHTML() {
      return this.Canvas.create({
        width: this.x + this.width || this.r * 2,
        height: this.y + this.height || this.r * 2,
        cview: this
      }).toHTML();
    }
  ],

  listeners: [
    {
      name: 'onChildUpdate',
      isFramed: true,
      code: function onChildUpdate() { this.pub('invalidated'); }
    }
  ]
});

foam.CLASS({
  package: 'foam.graphics',
  name: 'Box',
  extends: 'foam.graphics.CView',

  properties: [
    {
      name: 'width',
      class: 'Float'
    },
    {
      name: 'height',
      class: 'Float'
    },
    {
      name: 'border',
      value: 'black'
    }
  ],

  methods: [
    function paintSelf(x) {
      x.beginPath();
      x.rect(0, 0, this.width, this.height);
      if ( this.border ) x.stroke();
      if ( this.color ) x.fill();
    }
  ]
});

foam.CLASS({
  package: 'foam.graphics',
  name: 'Arc',
  extends: 'foam.graphics.CView',

  properties: [
    {
      name: 'radius',
      class: 'Float'
    },
    {
      name: 'start',
      class: 'Float'
    },
    {
      name: 'end',
      class: 'Float'
    },
    {
      name: 'arcWidth',
      class: 'Float'
    },
    {
      name: 'border',
      value: 'black'
    }
  ],

  methods: [
    function paintSelf(x) {
      x.beginPath();
      x.arc(0, 0, this.radius, this.start, this.end);

      if ( this.color ) x.fill();

      if ( this.border ) {
        x.lineWidth = this.arcWidth;
        x.stroke();
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.graphics',
  name: 'Circle',
  extends: 'foam.graphics.Arc',

  properties: [
    {
      name: 'start',
      value: 0
    },
    {
      name: 'end',
      value: 2*Math.PI
    }
  ],

  methods: [
    function intersects(c) {
      var r = this.radius + c.radius;
      if ( this.border ) r += this.arcWidth/2-1;
      if ( c.border    ) r += c.arcWidth/2-1;
      var dx = this.x-c.x;
      var dy = this.y-c.y;
      return dx * dx + dy * dy <= r * r;
    }
  ]
});

foam.CLASS({
  package: 'foam.graphics',
  name: 'Canvas',

  imports: [ 'getElementById' ],

  properties: [
    {
      name: 'id',
      factory: function() { return 'v' + this.$UID; }
    },
    {
      name: 'width',
    },
    {
      name: 'height',
    },
    {
      name: 'element',
      factory: function() {
        var e = this.getElementById(this.id);
        e.width = this.width;
        e.height = this.height;
        return e;
      },
      postSet: function(_, e) {
        e.width = this.width;
        e.height = this.height;
      }
    },
    {
      name: 'context',
      factory: function() {
        return this.element.getContext('2d');
      }
    },
    {
      name: 'cview',
      postSet: function(o, n) {
        o && o.invalidated.unsub(this.paint);
        n && n.invalidated.sub(this.paint);
        this.paint();
      }
    }
  ],

  methods: [
    function erase() {
      this.element.width = this.element.width;
    }
  ],

  listeners: [
    {
      name: 'paint',
      isFramed: true,
      code: function paintCanvas() {
        this.erase();
        this.cview && this.cview.paint(this.context);
      }
    }
  ],

  templates: [
    {
      name: 'toHTML',
      template: '<canvas id="<%= this.id %>"></canvas>'
    }
  ]
});

foam.CLASS({
  package: 'foam.graphics',
  name: 'Gradient',

  properties: [
    'x0', 'y0', 'r0',
    'x1', 'y1', 'r1',
    {
      name: 'radial',
      class: 'Boolean',
      value: false
    },
    {
      name: 'colors',
      factory: function() {
        return [];
      }
    }
  ],

  methods: [
    function toCanvasStyle(x) {
      var t = this;
      var g = this.radial ?
        x.createRadialGradient(t.x0, t.y0, t.r0, t.x1, t.y1, t.r1) :
        x.createLinearGradient(t.x0, t.y0, t.x1, t.y1) ;

      for ( var i = 0 ; i < t.colors.length ; i++ )
        g.addColorStop(t.colors[i][0], t.colors[i][1]);

      return g;
    }
  ]
});

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

foam.CLASS({
  package: 'foam.graphics',
  name: 'Transform',

  properties: [
    { class: 'Simple', name: 'a' },
    { class: 'Simple', name: 'b' },
    { class: 'Simple', name: 'c' },
    { class: 'Simple', name: 'd' },
    { class: 'Simple', name: 'e' },
    { class: 'Simple', name: 'f' },
    { class: 'Simple', name: 'g' },
    { class: 'Simple', name: 'h' },
    { class: 'Simple', name: 'i' }
  ],

  methods: [
    function initArgs() {
      this.a = 1; this.b = 0; this.c = 0;
      this.d = 0; this.e = 1; this.f = 0;
      this.g = 0; this.h = 0; this.i = 1;
    },

    function mul(a, b, c, d, e, f, g, h, i) {
      var ta = this.a, tb = this.b, tc = this.c,
          td = this.d, te = this.e, tf = this.f,
          tg = this.g, th = this.h, ti = this.i;

      this.a = ta * a + tb * d + tc * g;
      this.b = ta * b + tb * e + tc * h;
      this.c = ta * c + tb * f + tc * i;

      this.d = td * a + te * d + tf * g;
      this.e = td * b + te * e + tf * h;
      this.f = td * c + te * f + tf * i;

      this.g = tg * a + th * d + ti * g;
      this.h = tg * b + th * e + ti * h;
      this.i = tg * c + th * f + ti * i;

      return this;
    },

    function affine(m) {
      return this.mul(m.a, m.b, m.c, m.d, m.e, m.f, m.g, m.h, m.i);
    },

    function invert() {
      // a b c    a d g
      // d e f -> b e h
      // g h i    c f i
      var tmp = this.b;
      this.b = this.d;
      this.d = tmp;

      tmp = this.c;
      this.c = this.g;
      this.g = tmp;

      tmp = this.f;
      this.f = this.h;
      this.h = tmp;
      return this;
    },

    function reset() {
      this.initArgs();
      return this;
    },

    function translate(dx, dy) {
      if ( ! dx && ! dy ) return;
      this.mul(1, 0, dx, 0, 1, dy, 0, 0, 1);
    },

    function skew(x, y) {
      if ( ! x && ! y ) return;
      this.mul(1, x, 0, y, 1, 0, 0, 0, 1);
    },

    function scale(x, y) {
      if ( x === 1 && y === 1 ) return;
      this.mul(x, 0, 0, 0, y, 0, 0, 0, 1);
    },

    function rotate(a) {
      if ( ! a ) return;
      this.mul(Math.cos(a), Math.sin(a), 0, -Math.sin(a), Math.cos(a), 0, 0, 0, 1);
    }
  ]
});

foam.CLASS({
  package: 'foam.graphics',
  name: 'CView',

  requires: [
    'foam.graphics.Canvas',
    'foam.graphics.Transform'
  ],

  topics: [ 'invalidated' ],

  properties: [
    {
      name: 'rotation',
      class: 'Float'
    },
    {
      name: 'originX',
      class: 'Float'
    },
    {
      name: 'originY',
      class: 'Float'
    },
    {
      name: 'scaleX',
      class: 'Float',
      value: 1
    },
    {
      name: 'scaleY',
      class: 'Float',
      value: 1
    },
    {
      name: 'skewX',
      class: 'Float'
    },
    {
      name: 'skewY',
      class: 'Float'
    },
    {
      name: 'x',
      class: 'Float'
    },
    {
      name: 'y',
      class: 'Float'
    },
    {
      name: 'alpha',
      class: 'Float',
      value: 1
    },
    {
      name: 'border'
    },
    {
      name: 'color'
    },
    {
      name: 'shadowColor'
    },
    {
      name: 'shadowBlur'
    },
    {
      name: 'children',
      factory: function() { return []; },
      postSet: function(o, n) {
        for ( var i = 0 ; o && i < o.length ; i++ ) this.removeChild_(o[i]);
        for ( var i = 0 ; n && i < n.length ; i++ ) this.addChild_(n[i]);
      }
    },
    {
      name: 'state',
      value: 'initial'
    },
    {
      name: 'transform_',
      factory: function() { return this.Transform.create(); }
    },
    {
      name: 'transform',
      getter: function getTransform() {
        var t = this.transform_.reset();

        t.translate(this.x, this.y);
        t.rotate(this.rotation);
        t.skew(this.skewX, this.skewY);
        t.scale(this.scaleX, this.scaleY);
        t.translate(-this.originX, -this.originY);

        return t;
      }
    }
  ],

  methods: [
    function initCView() {
/*
      this.propertyChange.sub(function() {
        this.pub('invalidated');
      }.bind(this));
*/
    },

    function maybeInitCView() {
      if ( this.state == 'initial' ) {
        this.initCView();
        this.state = 'initailized'
      }
    },

    function paint(x) {
      this.maybeInitCView();
      x.save();
      x.globalAlpha *= this.alpha;
      x.strokeStyle = ( this.border && this.border.toCanvasStyle ) ?
        this.border.toCanvasStyle(x) : this.border;
      x.fillStyle = ( this.color && this.color.toCanvasStyle ) ?
        this.color.toCanvasStyle(x) : this.color;
      this.doTransform(x);
      if ( this.shadowColor && this.shadowBlur ) {
        x.shadowColor = this.shadowColor;
        x.shadowBlur  = this.shadowBlur;
      }
      this.paintSelf(x);
      this.paintChildren(x);
      x.restore();
    },

    function doTransform(x) {
      var t = this.transform;
      x.transform(t.a, t.d, t.b, t.e, t.c, t.f);
    },

    function paintChildren(x) {
      for ( var i = 0 ; i < this.children.length ; i++ ) {
        this.children[i].paint(x);
      }
    },

    function addChild_(c) {
      c.invalidated.sub(this.onChildUpdate);
      return c;
    },

    function removeChild_(c) {
      c.invalidated.unsub(this.onChildUpdate);
      return c;
    },

    function addChildren() {
      for ( var i = 0 ; i < arguments.length ; i++ ) {
        this.children.push(arguments[i]);
        // this.addChild_(arguments[i]);
      }
    },

    function paintSelf(x) {},

    function hsl(h, s, l) {
      return 'hsl(' + h + ',' + s + '%,' + l + '%)';
    },

    // FUTURE: Replace with toE() when/if ready.
    function toHTML() {
      return this.Canvas.create({
        width: this.x + this.width || this.r * 2,
        height: this.y + this.height || this.r * 2,
        cview: this
      }).toHTML();
    }
  ],

  listeners: [
    {
      name: 'onChildUpdate',
      isFramed: true,
      code: function onChildUpdate() { this.pub('invalidated'); }
    }
  ]
});

foam.CLASS({
  package: 'foam.graphics',
  name: 'Box',
  extends: 'foam.graphics.CView',

  properties: [
    {
      name: 'width',
      class: 'Float'
    },
    {
      name: 'height',
      class: 'Float'
    },
    {
      name: 'border',
      value: 'black'
    }
  ],

  methods: [
    function paintSelf(x) {
      x.beginPath();
      x.rect(0, 0, this.width, this.height);
      if ( this.border ) x.stroke();
      if ( this.color ) x.fill();
    }
  ]
});

foam.CLASS({
  package: 'foam.graphics',
  name: 'Arc',
  extends: 'foam.graphics.CView',

  properties: [
    {
      name: 'radius',
      class: 'Float'
    },
    {
      name: 'start',
      class: 'Float'
    },
    {
      name: 'end',
      class: 'Float'
    },
    {
      name: 'arcWidth',
      class: 'Float'
    },
    {
      name: 'border',
      value: 'black'
    }
  ],

  methods: [
    function paintSelf(x) {
      x.beginPath();
      x.arc(0, 0, this.radius, this.start, this.end);

      if ( this.color ) x.fill();

      if ( this.border ) {
        x.lineWidth = this.arcWidth;
        x.stroke();
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.graphics',
  name: 'Circle',
  extends: 'foam.graphics.Arc',

  properties: [
    {
      name: 'start',
      value: 0
    },
    {
      name: 'end',
      value: 2*Math.PI
    }
  ],

  methods: [
    function intersects(c) {
      var r = this.radius + c.radius;
      if ( this.border ) r += this.arcWidth/2-1;
      if ( c.border    ) r += c.arcWidth/2-1;
      var dx = this.x-c.x;
      var dy = this.y-c.y;
      return dx * dx + dy * dy <= r * r;
    }
  ]
});

foam.CLASS({
  package: 'foam.graphics',
  name: 'Canvas',

  imports: [ 'getElementById' ],

  properties: [
    {
      name: 'id',
      factory: function() { return 'v' + this.$UID; }
    },
    {
      name: 'width',
    },
    {
      name: 'height',
    },
    {
      name: 'element',
      factory: function() {
        var e = this.getElementById(this.id);
        e.width = this.width;
        e.height = this.height;
        return e;
      },
      postSet: function(_, e) {
        e.width = this.width;
        e.height = this.height;
      }
    },
    {
      name: 'context',
      factory: function() {
        return this.element.getContext('2d');
      }
    },
    {
      name: 'cview',
      postSet: function(o, n) {
        o && o.invalidated.unsub(this.paint);
        n && n.invalidated.sub(this.paint);
        this.paint();
      }
    }
  ],

  methods: [
    function erase() {
      this.element.width = this.element.width;
    }
  ],

  listeners: [
    {
      name: 'paint',
      isFramed: true,
      code: function paintCanvas() {
        this.erase();
        this.cview && this.cview.paint(this.context);
      }
    }
  ],

  templates: [
    {
      name: 'toHTML',
      template: '<canvas id="<%= this.id %>"></canvas>'
    }
  ]
});

foam.CLASS({
  package: 'foam.graphics',
  name: 'Gradient',

  properties: [
    'x0', 'y0', 'r0',
    'x1', 'y1', 'r1',
    {
      name: 'radial',
      class: 'Boolean',
      value: false
    },
    {
      name: 'colors',
      factory: function() {
        return [];
      }
    }
  ],

  methods: [
    function toCanvasStyle(x) {
      var t = this;
      var g = this.radial ?
        x.createRadialGradient(t.x0, t.y0, t.r0, t.x1, t.y1, t.r1) :
        x.createLinearGradient(t.x0, t.y0, t.x1, t.y1) ;

      for ( var i = 0 ; i < t.colors.length ; i++ )
        g.addColorStop(t.colors[i][0], t.colors[i][1]);

      return g;
    }
  ]
});

// TODO: add configurable repaint strategy. Ex. explicit, on property change, on child change
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012 mono
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

var Pinboard = {};

Pinboard.VERSION = 20120812;
Pinboard.POSTS_STORE = 'Posts';

Pinboard.STARTUP_DELAY = 5;
Pinboard.RETRY_DELAY = 5;
Pinboard.UPDATE_INTERVAL = 30;

Pinboard.Uris = {};

Pinboard.Uris.postsUpdate = function(params) {
  return 'https://api.pinboard.in/v1/posts/update?' + Utils.buildQuery(params);
};

Pinboard.Uris.postsAll = function(params) {
  return 'https://api.pinboard.in/v1/posts/all?' + Utils.buildQuery(params);
};

Pinboard.initialize = function(onsuccess) {
  Pinboard.storage.get('version', function(data) {
    switch(data.version) {
      case Pinboard.VERSION:
        Pinboard.load(onsuccess);
        break;

      case 20120313:
        Pinboard.storage.remove([ 'login' ], function() {
          Pinboard.storage.set({ 'version': Pinboard.VERSION, 'posts': [] }, function() {
            Pinboard.load(onsuccess);
          });
        });
        break;

      default:
        Pinboard.storage.clear(function() {
          Pinboard.storage.set({ 'version': Pinboard.VERSION, 'posts': [] }, function() {
            Pinboard.load(onsuccess);
          });
        });
        break;
    }
  });
};

Pinboard.load = function(onsuccess) {
  Pinboard.storage.get('posts', function(data) {
    Pinboard.sortPosts(data.posts);
    onsuccess(data.posts);
  });
};

Pinboard.store = function(posts, force) {
  Pinboard.initialize(function(base) {
    if(posts.length === 0) {
      return;
    }
    if(!force) {
      base.forEach(function(post) { posts.push(post); });
    }
    Pinboard.sortPosts(posts);
    Pinboard.storage.set({
      'version': Pinboard.VERSION,
      'posts': posts
    });
  });
};

Pinboard.loggedIn = function(onloggedin, onnotloggedin) {
  Pinboard.storage.get('login', function(data) {
    if(data && data.login && data.login.token) {
      onloggedin(data.login.token.split(':')[0]);
    }
    else {
      onnotloggedin();
    }
  });
};

Pinboard.update = function(token, onsuccess, onerror, fromDt) {
  if(!fromDt) {
    Pinboard.update(token, onsuccess, onerror);
    return;
  }
  var params = { format: 'json', auth_token: token };
  var request = new XMLHttpRequest();
  request.open('GET', Pinboard.Uris.postsUpdate(params), true);
  request.onreadystatechange = function() {
    if(request.readyState == 4) {
      if(request.status == 200) {
        if(JSON.parse(request.responseText).update_time > fromDt) {
          Pinboard.fetch(token, onsuccess, onerror, fromDt);
        }
        else {
          onsuccess(request.statusText);
        }
      }
      else {
        onerror(request.statusText);
      }
    }
  };
  request.send(null);
};

Pinboard.fetch = function(token, onsuccess, onerror, fromDt) {
  var params = { format: 'json', auth_token: token };
  if(fromDt) {
    params.fromdt = fromDt;
  }
  var request = new XMLHttpRequest();
  request.open('GET', Pinboard.Uris.postsAll(params), true);
  request.onreadystatechange = function() {
    if(request.readyState == 4) {
      if(request.status == 200) {
        var posts = JSON.parse(request.responseText);
        Pinboard.store(posts, !fromDt);
        Pinboard.storage.set({ 'updated': posts[0].time }, function() {
          onsuccess(request.statusText);
        });
      }
      else {
        onerror(request.statusText);
      }
    }
  };
  request.send(null);
};

Pinboard.login = function(token, onsuccess, onerror) {
  Pinboard.update(token,
    function(message) {
      var login = {
        token: token
      };
      Pinboard.storage.set({ 'login': login }, function() {
        Pinboard.requestAutoUpdate(60 * 60 * 1000);
        onsuccess(message);
      });
    },
    onerror);
};

Pinboard.sortPosts = function(posts) {
  posts.sort(function(a, b) {
    if(a.time < b.time) {
      return 1;
    }
    else if(a.time > b.time) {
      return -1;
    }
  return 0;
  });
};

Pinboard.logout = function() {
  Pinboard.storage.clear();
  Pinboard.cancelAutoUpdate();
};

Pinboard.autoUpdate = function(onsuccess, onerror, force) {
  Pinboard.updateTimer = undefined;
  Pinboard.storage.get([ 'login', 'updated' ], function(data) {
    if((!data && data.login)) {
      return;
    }
    var fromDt = !force ? data.updated : undefined;
    Pinboard.update(data.login.token,
      function(message) {
        Pinboard.retryCount = 0;
        Pinboard.requestAutoUpdate(Pinboard.UPDATE_DELAY * 60 * 1000);
        if(onsuccess) {
          onsuccess(message);
        }
      },
      function(message) {
        if(Pinboard.retryCount <= 12) {
          Pinboard.retryCount += 1;
        }
        Pinboard.requestAutoUpdate(Pinboard.retryCount * Pinboard.RETRY_DELAY * 60 * 1000);
        if(onerror) {
          onerror(message);
        }
      }, fromDt);
  });
};

Pinboard.forceUpdate = function(onsuccess, onerror) {
  Pinboard.cancelAutoUpdate();
  Pinboard.autoUpdate(onsuccess, onerror, true);
};

Pinboard.loginRequired = function(onloggedin, onnotloggedin) {
  Pinboard.loggedIn(function(user) {
    if(onloggedin) {
      onloggedin(user);
    }
  },
  function() {
    chrome.tabs.create({ url: chrome.extension.getURL('/login.html') });
    if(onnotloggedin) {
      onnotloggedin();
    }
  });
};

Pinboard.requestAutoUpdate = function(time) {
  Pinboard.cancelAutoUpdate();
  Pinboard.updateTimer = setTimeout(Pinboard.autoUpdate, time);
};

Pinboard.cancelAutoUpdate = function() {
  if(Pinboard.updateTimer) {
    clearTimeout(Pinboard.updateTimer);
    Pinboard.updateTimer = undefined;
  }
};

Pinboard.storage = chrome.storage.local;
Pinboard.retryCount = 0;
Pinboard.updateTimer = undefined;

Pinboard.initialize(function() {
  Pinboard.loginRequired(function() {
    Pinboard.updateTimer = setTimeout(function() {
      Pinboard.autoUpdate(undefined, undefined, true);
    }, Pinboard.STARTUP_DELAY * 60 * 1000);
  });
});

<commit_msg>fix stack overflowing<commit_after>/*
 * Copyright (c) 2012 mono
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

var Pinboard = {};

Pinboard.VERSION = 20120812;
Pinboard.POSTS_STORE = 'Posts';

Pinboard.STARTUP_DELAY = 5;
Pinboard.RETRY_DELAY = 5;
Pinboard.UPDATE_INTERVAL = 30;

Pinboard.Uris = {};

Pinboard.Uris.postsUpdate = function(params) {
  return 'https://api.pinboard.in/v1/posts/update?' + Utils.buildQuery(params);
};

Pinboard.Uris.postsAll = function(params) {
  return 'https://api.pinboard.in/v1/posts/all?' + Utils.buildQuery(params);
};

Pinboard.initialize = function(onsuccess) {
  Pinboard.storage.get('version', function(data) {
    switch(data.version) {
      case Pinboard.VERSION:
        Pinboard.load(onsuccess);
        break;

      case 20120313:
        Pinboard.storage.remove([ 'login' ], function() {
          Pinboard.storage.set({ 'version': Pinboard.VERSION, 'posts': [] }, function() {
            Pinboard.load(onsuccess);
          });
        });
        break;

      default:
        Pinboard.storage.clear(function() {
          Pinboard.storage.set({ 'version': Pinboard.VERSION, 'posts': [] }, function() {
            Pinboard.load(onsuccess);
          });
        });
        break;
    }
  });
};

Pinboard.load = function(onsuccess) {
  Pinboard.storage.get('posts', function(data) {
    Pinboard.sortPosts(data.posts);
    onsuccess(data.posts);
  });
};

Pinboard.store = function(posts, force) {
  Pinboard.initialize(function(base) {
    if(posts.length === 0) {
      return;
    }
    if(!force) {
      base.forEach(function(post) { posts.push(post); });
    }
    Pinboard.sortPosts(posts);
    Pinboard.storage.set({
      'version': Pinboard.VERSION,
      'posts': posts
    });
  });
};

Pinboard.loggedIn = function(onloggedin, onnotloggedin) {
  Pinboard.storage.get('login', function(data) {
    if(data && data.login && data.login.token) {
      onloggedin(data.login.token.split(':')[0]);
    }
    else {
      onnotloggedin();
    }
  });
};

Pinboard.update = function(token, onsuccess, onerror, fromDt) {
  if(!fromDt) {
    Pinboard.fetch(token, onsuccess, onerror);
    return;
  }
  var params = { format: 'json', auth_token: token };
  var request = new XMLHttpRequest();
  request.open('GET', Pinboard.Uris.postsUpdate(params), true);
  request.onreadystatechange = function() {
    if(request.readyState == 4) {
      if(request.status == 200) {
        if(JSON.parse(request.responseText).update_time > fromDt) {
          Pinboard.fetch(token, onsuccess, onerror, fromDt);
        }
        else {
          onsuccess(request.statusText);
        }
      }
      else {
        onerror(request.statusText);
      }
    }
  };
  request.send(null);
};

Pinboard.fetch = function(token, onsuccess, onerror, fromDt) {
  var params = { format: 'json', auth_token: token };
  if(fromDt) {
    params.fromdt = fromDt;
  }
  var request = new XMLHttpRequest();
  request.open('GET', Pinboard.Uris.postsAll(params), true);
  request.onreadystatechange = function() {
    if(request.readyState == 4) {
      if(request.status == 200) {
        var posts = JSON.parse(request.responseText);
        Pinboard.store(posts, !fromDt);
        Pinboard.storage.set({ 'updated': posts[0].time }, function() {
          onsuccess(request.statusText);
        });
      }
      else {
        onerror(request.statusText);
      }
    }
  };
  request.send(null);
};

Pinboard.login = function(token, onsuccess, onerror) {
  Pinboard.update(token,
    function(message) {
      var login = {
        token: token
      };
      Pinboard.storage.set({ 'login': login }, function() {
        Pinboard.requestAutoUpdate(60 * 60 * 1000);
        onsuccess(message);
      });
    },
    onerror);
};

Pinboard.sortPosts = function(posts) {
  posts.sort(function(a, b) {
    if(a.time < b.time) {
      return 1;
    }
    else if(a.time > b.time) {
      return -1;
    }
  return 0;
  });
};

Pinboard.logout = function() {
  Pinboard.storage.clear();
  Pinboard.cancelAutoUpdate();
};

Pinboard.autoUpdate = function(onsuccess, onerror, force) {
  Pinboard.updateTimer = undefined;
  Pinboard.storage.get([ 'login', 'updated' ], function(data) {
    if((!data && data.login)) {
      return;
    }
    var fromDt = !force ? data.updated : undefined;
    Pinboard.update(data.login.token,
      function(message) {
        Pinboard.retryCount = 0;
        Pinboard.requestAutoUpdate(Pinboard.UPDATE_DELAY * 60 * 1000);
        if(onsuccess) {
          onsuccess(message);
        }
      },
      function(message) {
        if(Pinboard.retryCount <= 12) {
          Pinboard.retryCount += 1;
        }
        Pinboard.requestAutoUpdate(Pinboard.retryCount * Pinboard.RETRY_DELAY * 60 * 1000);
        if(onerror) {
          onerror(message);
        }
      }, fromDt);
  });
};

Pinboard.forceUpdate = function(onsuccess, onerror) {
  Pinboard.cancelAutoUpdate();
  Pinboard.autoUpdate(onsuccess, onerror, true);
};

Pinboard.loginRequired = function(onloggedin, onnotloggedin) {
  Pinboard.loggedIn(function(user) {
    if(onloggedin) {
      onloggedin(user);
    }
  },
  function() {
    chrome.tabs.create({ url: chrome.extension.getURL('/login.html') });
    if(onnotloggedin) {
      onnotloggedin();
    }
  });
};

Pinboard.requestAutoUpdate = function(time) {
  Pinboard.cancelAutoUpdate();
  Pinboard.updateTimer = setTimeout(Pinboard.autoUpdate, time);
};

Pinboard.cancelAutoUpdate = function() {
  if(Pinboard.updateTimer) {
    clearTimeout(Pinboard.updateTimer);
    Pinboard.updateTimer = undefined;
  }
};

Pinboard.storage = chrome.storage.local;
Pinboard.retryCount = 0;
Pinboard.updateTimer = undefined;

Pinboard.initialize(function() {
  Pinboard.loginRequired(function() {
    Pinboard.updateTimer = setTimeout(function() {
      Pinboard.autoUpdate(undefined, undefined, true);
    }, Pinboard.STARTUP_DELAY * 60 * 1000);
  });
});

<|endoftext|>


<commit_before>import { introJs } from 'intro.js'
import isMobile from 'ismobilejs'
import { shouldEnableTracking, getTracker } from './tracker'
require('../../node_modules/intro.js/minified/introjs.min.css')

export function isTutorial () {
  return window.location.hash.endsWith('/?intro')
}

export function display (t) {
  const isSmall = document.querySelectorAll('.coz-nav')[0].offsetParent === null
  const isLandscape = window.innerWidth > window.innerHeight
  const cozyBarMenuClass = isSmall ? '.coz-bar-burger' : '[data-icon=icon-cube]'
  const cozyBarMenuButton = document.querySelectorAll(cozyBarMenuClass)[0]
  const tooltipClass = 'tooltip' + (isSmall ? 'Small' : '') + (isSmall && isLandscape ? 'Right' : 'Bottom')
  const shouldTrackTutorial = shouldEnableTracking()
  const pageURLsForTracking = [
    'tutorial/automate',
    'tutorial/apps'
  ]
  const tutorial = introJs()
  tutorial.setOptions({
    overlayOpacity: 0.7,
    showBullets: false,
    hidePrev: true,
    hideNext: true,
    exitOnEsc: false,
    exitOnOverlayClick: false,
    disableInteraction: true,
    doneLabel: `${t('tutorial.menu_apps.button')}`,
    nextLabel: `${t('tutorial.cozy_collect.button')}`,
    steps: [
      {
        element: document.querySelectorAll('.item-wrapper')[0],
        intro: `<h1>${t('tutorial.cozy_collect.title')}</h1><div>${t('tutorial.cozy_collect.text')}</div>`,
        tooltipClass: tooltipClass,
        position: isSmall && isLandscape ? 'right' : 'bottom'
      },
      {
        element: cozyBarMenuButton,
        intro: `<h1>${t('tutorial.menu_apps.title')}</h1><div>${t('tutorial.menu_apps.text')}</div>`,
        tooltipClass: isSmall ? 'tooltipCenter' : 'tooltipLeft',
        position: isSmall ? 'right' : 'bottom'
      }
    ]
  })
  .onafterchange((targetElement) => {
    if (shouldTrackTutorial) {
      let trackerInstance = getTracker()
      let stepIndex
      let steps =  tutorial._options.steps
      for (let i = 0, l = steps.length; i < l; ++i) {
        if (steps[i].element === targetElement) {
          stepIndex = i
          break
        }
      }

      const trackingURL = pageURLsForTracking[stepIndex]
      trackerInstance.push(['setCustomUrl', trackingURL])
      trackerInstance.push(['trackPageView'])
    }

    // The intro.js button for the last step is hidden, so we need to show it when we arrive on it
    const doneButton = document.querySelectorAll('.introjs-donebutton')[0]
    if (!doneButton) return // step 1, no done button yet

    if (targetElement.className === cozyBarMenuButton.className) {
      doneButton.classList.remove('introjs-skipbutton')
    } else {
      doneButton.classList.add('introjs-skipbutton')
    }
  })
  .oncomplete(() => {
    cozyBarMenuButton.click()
    window.location.hash = '#/discovery'
  })
  .start()
  if (isMobile.phone) {
    const clickZone = '.introjs-disableInteraction, .introjs-overlay, .introjs-tooltiptext, .introjs-tooltipbuttons'
    const clickAction = (e) => {
      if (e.srcElement.tagName !== 'A') {
        e.stopPropagation()
        tutorial.nextStep()
      }
    }
    for (const elem of document.querySelectorAll(clickZone)) {
      elem.onclick = clickAction
    }
  }
}
<commit_msg>fix: handle addblocker case<commit_after>import { introJs } from 'intro.js'
import isMobile from 'ismobilejs'
import { shouldEnableTracking, getTracker } from './tracker'
require('../../node_modules/intro.js/minified/introjs.min.css')

export function isTutorial () {
  return window.location.hash.endsWith('/?intro')
}

export function display (t) {
  const isSmall = document.querySelectorAll('.coz-nav')[0].offsetParent === null
  const isLandscape = window.innerWidth > window.innerHeight
  const cozyBarMenuClass = isSmall ? '.coz-bar-burger' : '[data-icon=icon-cube]'
  const cozyBarMenuButton = document.querySelectorAll(cozyBarMenuClass)[0]
  const tooltipClass = 'tooltip' + (isSmall ? 'Small' : '') + (isSmall && isLandscape ? 'Right' : 'Bottom')
  const shouldTrackTutorial = shouldEnableTracking()
  const trackerInstance = getTracker()
  const pageURLsForTracking = [
    'tutorial/automate',
    'tutorial/apps'
  ]
  const tutorial = introJs()
  tutorial.setOptions({
    overlayOpacity: 0.7,
    showBullets: false,
    hidePrev: true,
    hideNext: true,
    exitOnEsc: false,
    exitOnOverlayClick: false,
    disableInteraction: true,
    doneLabel: `${t('tutorial.menu_apps.button')}`,
    nextLabel: `${t('tutorial.cozy_collect.button')}`,
    steps: [
      {
        element: document.querySelectorAll('.item-wrapper')[0],
        intro: `<h1>${t('tutorial.cozy_collect.title')}</h1><div>${t('tutorial.cozy_collect.text')}</div>`,
        tooltipClass: tooltipClass,
        position: isSmall && isLandscape ? 'right' : 'bottom'
      },
      {
        element: cozyBarMenuButton,
        intro: `<h1>${t('tutorial.menu_apps.title')}</h1><div>${t('tutorial.menu_apps.text')}</div>`,
        tooltipClass: isSmall ? 'tooltipCenter' : 'tooltipLeft',
        position: isSmall ? 'right' : 'bottom'
      }
    ]
  })
  .onafterchange((targetElement) => {
    if (shouldTrackTutorial && trackerInstance) {
      let stepIndex
      let steps =  tutorial._options.steps
      for (let i = 0, l = steps.length; i < l; ++i) {
        if (steps[i].element === targetElement) {
          stepIndex = i
          break
        }
      }

      const trackingURL = pageURLsForTracking[stepIndex]
      trackerInstance.push(['setCustomUrl', trackingURL])
      trackerInstance.push(['trackPageView'])
    }

    // The intro.js button for the last step is hidden, so we need to show it when we arrive on it
    const doneButton = document.querySelectorAll('.introjs-donebutton')[0]
    if (!doneButton) return // step 1, no done button yet

    if (targetElement.className === cozyBarMenuButton.className) {
      doneButton.classList.remove('introjs-skipbutton')
    } else {
      doneButton.classList.add('introjs-skipbutton')
    }
  })
  .oncomplete(() => {
    cozyBarMenuButton.click()
    window.location.hash = '#/discovery'
  })
  .start()
  if (isMobile.phone) {
    const clickZone = '.introjs-disableInteraction, .introjs-overlay, .introjs-tooltiptext, .introjs-tooltipbuttons'
    const clickAction = (e) => {
      if (e.srcElement.tagName !== 'A') {
        e.stopPropagation()
        tutorial.nextStep()
      }
    }
    for (const elem of document.querySelectorAll(clickZone)) {
      elem.onclick = clickAction
    }
  }
}
<|endoftext|>


<commit_before>/* @flow */

export function baseWarn (msg: string) {
  console.error(`[Vue parser]: ${msg}`)
}

export function addProp (el: ASTElement, name: string, value: string) {
  (el.props || (el.props = [])).push({ name, value })
}

export function addAttr (el: ASTElement, name: string, value: string) {
  (el.attrs || (el.attrs = [])).push({ name, value })
}

export function addStaticAttr (el: ASTElement, name: string, value: string) {
  (el.staticAttrs || (el.staticAttrs = [])).push({ name, value })
}

export function addDirective (
  el: ASTElement,
  name: string,
  value: string,
  arg: ?string,
  modifiers: ?{ [key: string]: true }
) {
  (el.directives || (el.directives = [])).push({ name, value, arg, modifiers })
}

export function addStyleBinding (el: ASTElement, name: string, value: string) {
  const code = `"${name}":${value}`
  el.styleBinding = el.styleBinding
    ? el.styleBinding.replace(/}\s?$/, `,${code}}`)
    : `{${code}}`
}

export function addHook (el: ASTElement, name: string, code: string) {
  const hooks = el.hooks || (el.hooks = {})
  const hook = hooks[name]
  if (hook) {
    hook.push(code)
  } else {
    hooks[name] = [code]
  }
}

export function addHandler (
  el: ASTElement,
  name: string,
  value: string,
  modifiers: ?{ [key: string]: true }
) {
  const events = el.events || (el.events = {})
  // check capture modifier
  if (modifiers && modifiers.capture) {
    delete modifiers.capture
    name = '!' + name // mark the event as captured
  }
  const newHandler = { value, modifiers }
  const handlers = events[name]
  if (Array.isArray(handlers)) {
    handlers.push(newHandler)
  } else if (handlers) {
    events[name] = [handlers, newHandler]
  } else {
    events[name] = newHandler
  }
}

export function getBindingAttr (
  el: ASTElement,
  name: string,
  getStatic?: boolean
) {
  const staticValue = getStatic !== false && getAndRemoveAttr(el, name)
  return staticValue || staticValue === ''
    ? JSON.stringify(staticValue)
    : (getAndRemoveAttr(el, ':' + name) || getAndRemoveAttr(el, 'v-bind:' + name))
}

export function getAndRemoveAttr (el: ASTElement, name: string) {
  let val
  if ((val = el.attrsMap[name]) != null) {
    el.attrsMap[name] = null
    const list = el.attrsList
    for (let i = 0, l = list.length; i < l; i++) {
      if (list[i].name === name) {
        list.splice(i, 1)
        break
      }
    }
  }
  return val
}
<commit_msg>adjust binding attr retrieval priority<commit_after>/* @flow */

export function baseWarn (msg: string) {
  console.error(`[Vue parser]: ${msg}`)
}

export function addProp (el: ASTElement, name: string, value: string) {
  (el.props || (el.props = [])).push({ name, value })
}

export function addAttr (el: ASTElement, name: string, value: string) {
  (el.attrs || (el.attrs = [])).push({ name, value })
}

export function addStaticAttr (el: ASTElement, name: string, value: string) {
  (el.staticAttrs || (el.staticAttrs = [])).push({ name, value })
}

export function addDirective (
  el: ASTElement,
  name: string,
  value: string,
  arg: ?string,
  modifiers: ?{ [key: string]: true }
) {
  (el.directives || (el.directives = [])).push({ name, value, arg, modifiers })
}

export function addStyleBinding (el: ASTElement, name: string, value: string) {
  const code = `"${name}":${value}`
  el.styleBinding = el.styleBinding
    ? el.styleBinding.replace(/}\s?$/, `,${code}}`)
    : `{${code}}`
}

export function addHook (el: ASTElement, name: string, code: string) {
  const hooks = el.hooks || (el.hooks = {})
  const hook = hooks[name]
  if (hook) {
    hook.push(code)
  } else {
    hooks[name] = [code]
  }
}

export function addHandler (
  el: ASTElement,
  name: string,
  value: string,
  modifiers: ?{ [key: string]: true }
) {
  const events = el.events || (el.events = {})
  // check capture modifier
  if (modifiers && modifiers.capture) {
    delete modifiers.capture
    name = '!' + name // mark the event as captured
  }
  const newHandler = { value, modifiers }
  const handlers = events[name]
  if (Array.isArray(handlers)) {
    handlers.push(newHandler)
  } else if (handlers) {
    events[name] = [handlers, newHandler]
  } else {
    events[name] = newHandler
  }
}

export function getBindingAttr (
  el: ASTElement,
  name: string,
  getStatic?: boolean
): ?string {
  const dynamicValue =
    getAndRemoveAttr(el, ':' + name) ||
    getAndRemoveAttr(el, 'v-bind:' + name)
  if (dynamicValue != null) {
    return dynamicValue
  } else if (getStatic !== false) {
    const staticValue = getAndRemoveAttr(el, name)
    if (staticValue != null) {
      return JSON.stringify(staticValue)
    }
  }
}

export function getAndRemoveAttr (el: ASTElement, name: string): ?string {
  let val
  if ((val = el.attrsMap[name]) != null) {
    el.attrsMap[name] = null
    const list = el.attrsList
    for (let i = 0, l = list.length; i < l; i++) {
      if (list[i].name === name) {
        list.splice(i, 1)
        break
      }
    }
  }
  return val
}
<|endoftext|>


<commit_before>import { VisualizationComponent } from './../resplendent';

var d3 = require('d3');

class Dummy extends VisualizationComponent {
  construct (div, data) {
    super.construct(div, data);

    d3.select(this.div)
      .append('ul');

    if (data) {
      this.refresh(data);
    }
  }

  refresh (data) {
    let d = d3.select(this.div)
      .select('ul')
      .selectAll('li')
      .data(data);

    d.enter()
      .append('li');

    d.text((d) => d.text)
      .style('color', (d) => d.color);
  }
}

export { Dummy };
<commit_msg>Fix misspelling of "constructor"<commit_after>import { VisualizationComponent } from './../resplendent';

var d3 = require('d3');

class Dummy extends VisualizationComponent {
  constructor (div, data) {
    super(div, data);

    d3.select(this.div)
      .append('ul');

    if (data) {
      this.refresh(data);
    }
  }

  refresh (data) {
    let d = d3.select(this.div)
      .select('ul')
      .selectAll('li')
      .data(data);

    d.enter()
      .append('li');

    d.text((d) => d.text)
      .style('color', (d) => d.color);
  }
}

export { Dummy };
<|endoftext|>


<commit_before>import React, { Component, Fragment } from 'react'
import {
  Box,
  Button,
  Card,
  Container,
  Flex,
  Heading,
  Input,
  Label,
  LargeButton,
  Text,
  colors
} from '@hackclub/design-system'
import { Prompt } from 'react-router'
import MarkdownRenderer from 'components/MarkdownRenderer'

const SaveStatusText = Text.extend.attrs({
  f: [1, 3],
  p: 1,
  m: 2,
  bg: 'white',
  children: props => (props.saved ? 'Saved' : 'Saving…'),
  color: props => (props.saved ? 'slate' : 'primary')
})`
  position: fixed;
  bottom: 0;
  left: 0;
  border-style: solid;
  border-width: 1px;
  border-color: ${props =>
    props.theme.colors[props.saved ? 'slate' : 'primary']};
  border-radius: 4px;
  opacity: ${props => (props.saved ? 0 : 1)};
  transition-duration: ${props => (props.saved ? 2 : 1)}s;
  transition-delay: ${props => (props.saved ? 4 : 0)}s;
  transition-property: opacity;
  transition-timing-function: ease-in-out;
`

const SaveStatusLine = Box.extend`
  width: 100%;
  position: fixed;
  bottom: 0;
  left: 0;
  border-style: solid;
  border-width: 0;
  border-top-width: 1px;
  opacity: ${props => (props.saved ? 2 / 3 : 1)};
  transition-duration: 1s;
  color: ${props => props.theme.colors[props.saved ? 'slate' : 'primary']};
  box-shadow: 0 0 4px ${props => (props.saved ? '0px' : '2px')};
`

const SaveStatus = ({ saved, type = 'all' }) => (
  <Fragment>
    {['all', 'text'].indexOf(type) !== -1 && <SaveStatusText saved={saved} />}
    {['all', 'underline'].indexOf(type) !== -1 && (
      <SaveStatusLine saved={saved} />
    )}
  </Fragment>
)

export class AutoSaver extends Component {
  constructor(props) {
    super(props)
    this.state = {
      previousValues: props.values
    }
    this.autoSave = this.autoSave.bind(this)
  }

  componentWillMount() {
    const intervalId = setInterval(this.autoSave, 1000)
    this.setState({ intervalId })
  }

  componentWillUnmount() {
    clearInterval(this.state.intervalId)
  }

  autoSave() {
    const { handleSubmit, isSubmitting, values } = this.props
    const { previousValues } = this.state
    const unsavedChanges = previousValues !== values

    this.setState({ unsavedChanges })

    if (unsavedChanges && !isSubmitting) {
      /* This is super hacky and should be changed -- basically if we have a new
       * ID it's because the parent form is now holding a new record. We don't
       * want to save the new record though because it's just loaded, so we'll
       * set the previousValues to the new record's values */
      const changedRecord = previousValues.id !== values.id

      if (!changedRecord) {
        // We have to call handleSubmit this way because formik:
        // https://github.com/jaredpalmer/formik/issues/347
        handleSubmit({ preventDefault: () => null })
      }

      this.setState({
        previousValues: values
      })
    }
  }

  render() {
    const { unsavedChanges } = this.state
    const { saveNotification } = this.props
    return (
      <Fragment>
        <SaveStatus type={saveNotification} saved={!unsavedChanges} />
        {unsavedChanges && <ConfirmClose />}
      </Fragment>
    )
  }
}

export const Error = Text.span.extend.attrs({
  className: 'error',
  color: 'error',
  f: 1,
  ml: 1
})`
  text-transform: lowercase;
  &:before { content: '— '; }
`

export const Hint = Text.span.extend.attrs({
  color: 'slate',
  f: 1,
  mt: 1,
  align: 'left'
})`
  display: block;
  line-height: 1.375;
`

export class ConfirmClose extends Component {
  componentWillMount() {
    // https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm
    window.onbeforeunload = () => window.confirm()
  }

  componentWillUnmount() {
    window.onbeforeunload = null
  }

  render() {
    return (
      <Prompt message="Hold on, you're about to lose unsaved changes! Sure you want to leave?" />
    )
  }
}

export const Optional = () => (
  <Text.span
    className="optional"
    f={1}
    ml={1}
    color="muted"
    children="(optional)"
  />
)

export class Field extends Component {
  constructor(props) {
    super(props)
    this.onFocus = this.onFocus.bind(this)
    this.onBlur = this.onBlur.bind(this)
  }

  componentWillMount() {
    const { type, renderMarkdown } = this.props
    const Tag = Input.withComponent(
      ['textarea', 'select'].indexOf(type) === -1 ? 'input' : type
    )

    this.setState({ Tag, isEditing: false })
  }

  onBlur(e) {
    const { renderMarkdown, onBlur } = this.props
    if (renderMarkdown) {
      this.setState({ isEditing: false })
    }
    if (onBlur) {
      return onBlur(e)
    }
  }

  onFocus() {
    if (this.props.renderMarkdown) {
      this.setState({ isEditing: true })
    }
  }

  render() {
    const {
      type = 'text',
      name = 'name',
      label,
      p,
      children,
      error,
      hint,
      optional,
      value,
      renderMarkdown,
      bg,
      ...props
    } = this.props

    const { Tag, isEditing } = this.state

    return (

        <Flex style={{ display: 'inline' }} wrap>
          {label}
          {optional ? <Optional /> : null}
          {error && <Error children={error} />}
          {renderMarkdown && <Hint children="click to edit" />}
        </Flex>
        {renderMarkdown && (
          <Card
            onClick={this.onFocus}
            hidden={isEditing}
            boxShadowSize="sm"
            mt={1}
            p={[2, 3]}
            w={1}
            bg={bg}
            style={{ cursor: 'pointer' }}
          >
            <MarkdownRenderer content={value || ' '} />
          </Card>
        )}

          <Tag
            name={name}
            type={type}
            height={type === 'textarea' ? '10rem' : 'inherit'}
            placeholder={p}
            children={children}
            {...this.props}
            onBlur={this.onBlur}
            value={value}
            bg={bg}
          />

        {hint && <Hint children={hint} />}

    )
  }
}

export const Submit = Button.withComponent('input').extend.attrs({
  type: 'submit'
})``
Submit.lg = LargeButton.withComponent('input').extend.attrs({
  type: 'submit'
})``

export const FormWrapper = Flex.withComponent(Container).extend`
  flex-direction: column;
`

export const Form = Container.withComponent('form').extend.attrs({
  py: 4,
  px: 3,
  maxWidth: 42
})`
  display: grid;
  grid-gap: 1rem;
  ${({ theme }) => theme.mediaQueries[1]} {
    grid-template-columns: repeat(1, 1fr);
    h2, .textarea { grid-column: 1 / -1; }
  }
`

export const Subheading = Heading.h2.extend.attrs({
  f: 4,
  color: 'primary'
})`
  text-transform: capitalize;
`

const HeadingBox = Box.extend.attrs({
  mr: 3
})`
  text-align: left;
  order: 1;
  flex-grow: 0;
  flex-shrink: 0;
  flex-basis: auto;
  ${({ theme }) => theme.mediaQueries[1]} {
    flex-basis: 7rem;
    text-align: right;
  }
`

const FieldsBox = Box.extend.attrs({})`
  order: 2;
  flex-grow: 1;
`

export const Fieldset = props => (
  <Flex flexDirection={['column', 'row']}>
    <HeadingBox>
      <Subheading id={props.section}>{props.section}</Subheading>
    </HeadingBox>
    <FieldsBox>{props.children}</FieldsBox>
  </Flex>
)

export const Aside = props => <Box bg="snow" {...props} />
<commit_msg>Space out Fields<commit_after>import React, { Component, Fragment } from 'react'
import {
  Box,
  Button,
  Card,
  Container,
  Flex,
  Heading,
  Input,
  Label,
  LargeButton,
  Text,
  colors
} from '@hackclub/design-system'
import { Prompt } from 'react-router'
import MarkdownRenderer from 'components/MarkdownRenderer'

const SaveStatusText = Text.extend.attrs({
  f: [1, 3],
  p: 1,
  m: 2,
  bg: 'white',
  children: props => (props.saved ? 'Saved' : 'Saving…'),
  color: props => (props.saved ? 'slate' : 'primary')
})`
  position: fixed;
  bottom: 0;
  left: 0;
  border-style: solid;
  border-width: 1px;
  border-color: ${props =>
    props.theme.colors[props.saved ? 'slate' : 'primary']};
  border-radius: 4px;
  opacity: ${props => (props.saved ? 0 : 1)};
  transition-duration: ${props => (props.saved ? 2 : 1)}s;
  transition-delay: ${props => (props.saved ? 4 : 0)}s;
  transition-property: opacity;
  transition-timing-function: ease-in-out;
`

const SaveStatusLine = Box.extend`
  width: 100%;
  position: fixed;
  bottom: 0;
  left: 0;
  border-style: solid;
  border-width: 0;
  border-top-width: 1px;
  opacity: ${props => (props.saved ? 2 / 3 : 1)};
  transition-duration: 1s;
  color: ${props => props.theme.colors[props.saved ? 'slate' : 'primary']};
  box-shadow: 0 0 4px ${props => (props.saved ? '0px' : '2px')};
`

const SaveStatus = ({ saved, type = 'all' }) => (
  <Fragment>
    {['all', 'text'].indexOf(type) !== -1 && <SaveStatusText saved={saved} />}
    {['all', 'underline'].indexOf(type) !== -1 && (
      <SaveStatusLine saved={saved} />
    )}
  </Fragment>
)

export class AutoSaver extends Component {
  constructor(props) {
    super(props)
    this.state = {
      previousValues: props.values
    }
    this.autoSave = this.autoSave.bind(this)
  }

  componentWillMount() {
    const intervalId = setInterval(this.autoSave, 1000)
    this.setState({ intervalId })
  }

  componentWillUnmount() {
    clearInterval(this.state.intervalId)
  }

  autoSave() {
    const { handleSubmit, isSubmitting, values } = this.props
    const { previousValues } = this.state
    const unsavedChanges = previousValues !== values

    this.setState({ unsavedChanges })

    if (unsavedChanges && !isSubmitting) {
      /* This is super hacky and should be changed -- basically if we have a new
       * ID it's because the parent form is now holding a new record. We don't
       * want to save the new record though because it's just loaded, so we'll
       * set the previousValues to the new record's values */
      const changedRecord = previousValues.id !== values.id

      if (!changedRecord) {
        // We have to call handleSubmit this way because formik:
        // https://github.com/jaredpalmer/formik/issues/347
        handleSubmit({ preventDefault: () => null })
      }

      this.setState({
        previousValues: values
      })
    }
  }

  render() {
    const { unsavedChanges } = this.state
    const { saveNotification } = this.props
    return (
      <Fragment>
        <SaveStatus type={saveNotification} saved={!unsavedChanges} />
        {unsavedChanges && <ConfirmClose />}
      </Fragment>
    )
  }
}

export const Error = Text.span.extend.attrs({
  className: 'error',
  color: 'error',
  f: 1,
  ml: 1
})`
  text-transform: lowercase;
  &:before { content: '— '; }
`

export const Hint = Text.span.extend.attrs({
  color: 'slate',
  f: 1,
  mt: 1,
  align: 'left'
})`
  display: block;
  line-height: 1.375;
`

export class ConfirmClose extends Component {
  componentWillMount() {
    // https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm
    window.onbeforeunload = () => window.confirm()
  }

  componentWillUnmount() {
    window.onbeforeunload = null
  }

  render() {
    return (
      <Prompt message="Hold on, you're about to lose unsaved changes! Sure you want to leave?" />
    )
  }
}

export const Optional = () => (
  <Text.span
    className="optional"
    f={1}
    ml={1}
    color="muted"
    children="(optional)"
  />
)

export class Field extends Component {
  componentWillMount() {
    const { type, renderMarkdown } = this.props
    const Tag = Input.withComponent(
      ['textarea', 'select'].indexOf(type) === -1 ? 'input' : type
    )

    this.setState({ Tag, isEditing: false })
  }

  onBlur = e => {
    const { renderMarkdown, onBlur } = this.props
    if (renderMarkdown) {
      this.setState({ isEditing: false })
    }
    if (onBlur) {
      return onBlur(e)
    }
  }

  onFocus = () => {
    if (this.props.renderMarkdown) {
      this.setState({ isEditing: true })
    }
  }

  render() {
    const {
      type = 'text',
      name = 'name',
      label,
      p,
      children,
      error,
      hint,
      optional,
      value,
      renderMarkdown,
      bg,
      ...props
    } = this.props

    const { Tag, isEditing } = this.state

    return (

        <Flex style={{ display: 'inline' }} wrap>
          {label}
          {optional ? <Optional /> : null}
          {error && <Error children={error} />}
        </Flex>
        {renderMarkdown && (
          <Card
            onClick={this.onFocus}
            hidden={isEditing}
            boxShadowSize="sm"
            mt={1}
            p={[2, 3]}
            w={1}
            bg={bg}
            style={{ cursor: 'pointer' }}
          >
            <MarkdownRenderer content={value || ' '} />
          </Card>
        )}

          <Tag
            name={name}
            type={type}
            rows={type === 'textarea' ? 5 : null}
            placeholder={p}
            children={children}
            {...this.props}
            onBlur={this.onBlur}
            value={value}
            bg={bg}
          />

        {hint && <Hint children={hint} />}
        {renderMarkdown && <Hint children="click to edit" />}

    )
  }
}

export const Submit = Button.withComponent('input').extend.attrs({
  type: 'submit'
})``
Submit.lg = LargeButton.withComponent('input').extend.attrs({
  type: 'submit'
})``

export const FormWrapper = Flex.withComponent(Container).extend`
  flex-direction: column;
`

export const Form = Container.withComponent('form').extend.attrs({
  py: 4,
  px: 3,
  maxWidth: 42
})`
  display: grid;
  grid-gap: 1rem;
  ${({ theme }) => theme.mediaQueries[1]} {
    grid-template-columns: repeat(1, 1fr);
    h2, .textarea { grid-column: 1 / -1; }
  }
`

export const Subheading = Heading.h2.extend.attrs({
  f: 4,
  color: 'primary'
})`
  text-transform: capitalize;
`

const HeadingBox = Box.extend.attrs({
  mr: 3
})`
  text-align: left;
  order: 1;
  flex-grow: 0;
  flex-shrink: 0;
  flex-basis: auto;
  ${({ theme }) => theme.mediaQueries[1]} {
    flex-basis: 7rem;
    text-align: right;
  }
`

const FieldsBox = Box.extend.attrs({})`
  order: 2;
  flex-grow: 1;
`

export const Fieldset = props => (
  <Flex flexDirection={['column', 'row']}>
    <HeadingBox>
      <Subheading id={props.section}>{props.section}</Subheading>
    </HeadingBox>
    <FieldsBox>{props.children}</FieldsBox>
  </Flex>
)

export const Aside = props => <Box bg="snow" {...props} />
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Redirect} from 'react-router';

import Button from 'material-ui/Button';
import TextField from 'material-ui/TextField';
import GridList from 'material-ui/GridList';
import { withStyles } from 'material-ui/styles';

import {login as loginAction} from '../actions/AuthActions';

const styleSheet = theme => ({
    textField: {
        marginLeft: theme.spacing.unit,
        marginRight: theme.spacing.unit,
        width: 200,
    },
    loginButton: {
        marginTop: theme.spacing.unit,
    },
});

class Login extends Component {

    constructor(props) {
        super(props);
        this.state = {
            loginFail: false,
            user: {
                username: '',
                password: '',
            }
        };
        this.onSubmit = this.onSubmit.bind(this);
        this.onChange = this.onChange.bind(this);
    }

    /**
     * This method get called when username or password changes.
     * Internally it synchronizes the values of input boxes with component's state.
     * @param e event object
     */
    onChange(e) {
        const {value, name} = e.target;
        const newState = Object.assign({}, this.state);
        const {user} = newState;
        user[name] = value;
        this.setState(newState);
    }

    /**
     * This method get called when login button is pressed.
     * It validates the username and password. Upon successful login, user will be redirected to chat page.
     * Otherwise, stuck on the same page.
     * @param e event object
    */
    onSubmit(e) {
        e.preventDefault();
        const {login} = this.props;
        const {user} = this.state;
        login(user).then(success => {
            if(!success){
                const newState = Object.assign({}, this.state, {loginFail: true});
                this.setState(newState);
            }
        });
    }

    render() {
        const { user: { username, password }, loginFail} = this.state;
        const {authenticated, classes} = this.props;

        if (authenticated) {
            return <Redirect to="/chat" />;
        }

        return (

            <Grid container justify="center">
                {
                    loginFail && Wrong username or password! Please try again!
                }
                <TextField
                    name="username"
                    label="ユーザー名"
                    value={username}
                    className={classes.textField}
                    onChange={this.onChange}
                    margin="normal"
                />
            </Grid>
            <Grid container justify="center">
                <TextField
                    name="password"
                    type="password"
                    label="パスワード"
                    value={password}
                    className={classes.textField}
                    onChange={this.onChange}
                    margin="normal"
                />
            </Grid>
            <Grid container justify="center">
                <Button color="primary" raised onClick={this.onSubmit} className={classes.loginButton}>ログイン</Button>
            </Grid>

        );
    }
}

const mapStateToProps = ({session}) => ({
    authenticated: session.authenticated,
});

const mapDispatchToProps = (dispatch) => {
    return {
        login: (user, cb) => {
            return dispatch(loginAction(user, cb));
        },
    };
};

const StyledLogin = withStyles(styleSheet)(Login);

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(StyledLogin);
<commit_msg>Fix incorrect import<commit_after>import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Redirect} from 'react-router';

import Button from 'material-ui/Button';
import TextField from 'material-ui/TextField';
import Grid from 'material-ui/Grid';
import { withStyles } from 'material-ui/styles';

import {login as loginAction} from '../actions/AuthActions';

const styleSheet = theme => ({
    textField: {
        marginLeft: theme.spacing.unit,
        marginRight: theme.spacing.unit,
        width: 200,
    },
    loginButton: {
        marginTop: theme.spacing.unit,
    },
});

class Login extends Component {

    constructor(props) {
        super(props);
        this.state = {
            loginFail: false,
            user: {
                username: '',
                password: '',
            }
        };
        this.onSubmit = this.onSubmit.bind(this);
        this.onChange = this.onChange.bind(this);
    }

    /**
     * This method get called when username or password changes.
     * Internally it synchronizes the values of input boxes with component's state.
     * @param e event object
     */
    onChange(e) {
        const {value, name} = e.target;
        const newState = Object.assign({}, this.state);
        const {user} = newState;
        user[name] = value;
        this.setState(newState);
    }

    /**
     * This method get called when login button is pressed.
     * It validates the username and password. Upon successful login, user will be redirected to chat page.
     * Otherwise, stuck on the same page.
     * @param e event object
    */
    onSubmit(e) {
        e.preventDefault();
        const {login} = this.props;
        const {user} = this.state;
        login(user).then(success => {
            if(!success){
                const newState = Object.assign({}, this.state, {loginFail: true});
                this.setState(newState);
            }
        });
    }

    render() {
        const { user: { username, password }, loginFail} = this.state;
        const {authenticated, classes} = this.props;

        if (authenticated) {
            return <Redirect to="/chat" />;
        }

        return (

            <Grid container justify="center">
                {
                    loginFail && Wrong username or password! Please try again!
                }
                <TextField
                    name="username"
                    label="ユーザー名"
                    value={username}
                    className={classes.textField}
                    onChange={this.onChange}
                    margin="normal"
                />
            </Grid>
            <Grid container justify="center">
                <TextField
                    name="password"
                    type="password"
                    label="パスワード"
                    value={password}
                    className={classes.textField}
                    onChange={this.onChange}
                    margin="normal"
                />
            </Grid>
            <Grid container justify="center">
                <Button color="primary" raised onClick={this.onSubmit} className={classes.loginButton}>ログイン</Button>
            </Grid>

        );
    }
}

const mapStateToProps = ({session}) => ({
    authenticated: session.authenticated,
});

const mapDispatchToProps = (dispatch) => {
    return {
        login: (user, cb) => {
            return dispatch(loginAction(user, cb));
        },
    };
};

const StyledLogin = withStyles(styleSheet)(Login);

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(StyledLogin);
<|endoftext|>


<commit_before>import React, { Component } from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";
import ModalPortal from "./ModalPortal";
import * as ariaAppHider from "../helpers/ariaAppHider";
import SafeHTMLElement, { canUseDOM } from "../helpers/safeHTMLElement";

import { polyfill } from "react-lifecycles-compat";

export const portalClassName = "ReactModalPortal";
export const bodyOpenClassName = "ReactModal__Body--open";

const isReact16 = ReactDOM.createPortal !== undefined;

const getCreatePortal = () =>
  isReact16
    ? ReactDOM.createPortal
    : ReactDOM.unstable_renderSubtreeIntoContainer;

function getParentElement(parentSelector) {
  return parentSelector();
}

class Modal extends Component {
  static setAppElement(element) {
    ariaAppHider.setElement(element);
  }

  /* eslint-disable react/no-unused-prop-types */
  static propTypes = {
    isOpen: PropTypes.bool.isRequired,
    style: PropTypes.shape({
      content: PropTypes.object,
      overlay: PropTypes.object
    }),
    portalClassName: PropTypes.string,
    bodyOpenClassName: PropTypes.string,
    htmlOpenClassName: PropTypes.string,
    className: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.shape({
        base: PropTypes.string.isRequired,
        afterOpen: PropTypes.string.isRequired,
        beforeClose: PropTypes.string.isRequired
      })
    ]),
    overlayClassName: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.shape({
        base: PropTypes.string.isRequired,
        afterOpen: PropTypes.string.isRequired,
        beforeClose: PropTypes.string.isRequired
      })
    ]),
    appElement: PropTypes.instanceOf(SafeHTMLElement),
    onAfterOpen: PropTypes.func,
    onRequestClose: PropTypes.func,
    closeTimeoutMS: PropTypes.number,
    ariaHideApp: PropTypes.bool,
    shouldFocusAfterRender: PropTypes.bool,
    shouldCloseOnOverlayClick: PropTypes.bool,
    shouldReturnFocusAfterClose: PropTypes.bool,
    preventScroll: PropTypes.bool,
    parentSelector: PropTypes.func,
    aria: PropTypes.object,
    data: PropTypes.object,
    role: PropTypes.string,
    contentLabel: PropTypes.string,
    shouldCloseOnEsc: PropTypes.bool,
    overlayRef: PropTypes.func,
    contentRef: PropTypes.func
  };
  /* eslint-enable react/no-unused-prop-types */

  static defaultProps = {
    isOpen: false,
    portalClassName,
    bodyOpenClassName,
    role: "dialog",
    ariaHideApp: true,
    closeTimeoutMS: 0,
    shouldFocusAfterRender: true,
    shouldCloseOnEsc: true,
    shouldCloseOnOverlayClick: true,
    shouldReturnFocusAfterClose: true,
    preventScroll: false,
    parentSelector: () => document.body
  };

  static defaultStyles = {
    overlay: {
      position: "fixed",
      top: 0,
      left: 0,
      right: 0,
      bottom: 0,
      backgroundColor: "rgba(255, 255, 255, 0.75)"
    },
    content: {
      position: "absolute",
      top: "40px",
      left: "40px",
      right: "40px",
      bottom: "40px",
      border: "1px solid #ccc",
      background: "#fff",
      overflow: "auto",
      WebkitOverflowScrolling: "touch",
      borderRadius: "4px",
      outline: "none",
      padding: "20px"
    }
  };

  componentDidMount() {
    if (!canUseDOM) return;

    if (!isReact16) {
      this.node = document.createElement("div");
    }
    this.node.className = this.props.portalClassName;

    const parent = getParentElement(this.props.parentSelector);
    parent.appendChild(this.node);

    !isReact16 && this.renderPortal(this.props);
  }

  getSnapshotBeforeUpdate(prevProps) {
    const prevParent = getParentElement(prevProps.parentSelector);
    const nextParent = getParentElement(this.props.parentSelector);
    return { prevParent, nextParent };
  }

  componentDidUpdate(prevProps, _, snapshot) {
    if (!canUseDOM) return;
    const { isOpen, portalClassName } = this.props;

    if (prevProps.portalClassName !== portalClassName) {
      this.node.className = portalClassName;
    }

    const { prevParent, nextParent } = snapshot;
    if (nextParent !== prevParent) {
      prevParent.removeChild(this.node);
      nextParent.appendChild(this.node);
    }

    // Stop unnecessary renders if modal is remaining closed
    if (!prevProps.isOpen && !isOpen) return;

    !isReact16 && this.renderPortal(this.props);
  }

  componentWillUnmount() {
    if (!canUseDOM || !this.node || !this.portal) return;

    const state = this.portal.state;
    const now = Date.now();
    const closesAt =
      state.isOpen &&
      this.props.closeTimeoutMS &&
      (state.closesAt || now + this.props.closeTimeoutMS);

    if (closesAt) {
      if (!state.beforeClose) {
        this.portal.closeWithTimeout();
      }

      setTimeout(this.removePortal, closesAt - now);
    } else {
      this.removePortal();
    }
  }

  removePortal = () => {
    !isReact16 && ReactDOM.unmountComponentAtNode(this.node);
    const parent = getParentElement(this.props.parentSelector);
    if (parent) {
      parent.removeChild(this.node);
    } else {
      // eslint-disable-next-line no-console
      console.warn(
        'React-Modal: "parentSelector" prop did not returned any DOM ' +
          "element. Make sure that the parent element is unmounted to " +
          "avoid any memory leaks."
      );
    }
  };

  portalRef = ref => {
    this.portal = ref;
  };

  renderPortal = props => {
    const createPortal = getCreatePortal();
    const portal = createPortal(
      this,
      <ModalPortal defaultStyles={Modal.defaultStyles} {...props} />,
      this.node
    );
    this.portalRef(portal);
  };

  render() {
    if (!canUseDOM || !isReact16) {
      return null;
    }

    if (!this.node && isReact16) {
      this.node = document.createElement("div");
    }

    const createPortal = getCreatePortal();
    return createPortal(
      <ModalPortal
        ref={this.portalRef}
        defaultStyles={Modal.defaultStyles}
        {...this.props}
      />,
      this.node
    );
  }
}

polyfill(Modal);

export default Modal;
<commit_msg>[fixed] check before react-modal removal from parent element that parent has it<commit_after>import React, { Component } from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";
import ModalPortal from "./ModalPortal";
import * as ariaAppHider from "../helpers/ariaAppHider";
import SafeHTMLElement, { canUseDOM } from "../helpers/safeHTMLElement";

import { polyfill } from "react-lifecycles-compat";

export const portalClassName = "ReactModalPortal";
export const bodyOpenClassName = "ReactModal__Body--open";

const isReact16 = ReactDOM.createPortal !== undefined;

const getCreatePortal = () =>
  isReact16
    ? ReactDOM.createPortal
    : ReactDOM.unstable_renderSubtreeIntoContainer;

function getParentElement(parentSelector) {
  return parentSelector();
}

class Modal extends Component {
  static setAppElement(element) {
    ariaAppHider.setElement(element);
  }

  /* eslint-disable react/no-unused-prop-types */
  static propTypes = {
    isOpen: PropTypes.bool.isRequired,
    style: PropTypes.shape({
      content: PropTypes.object,
      overlay: PropTypes.object
    }),
    portalClassName: PropTypes.string,
    bodyOpenClassName: PropTypes.string,
    htmlOpenClassName: PropTypes.string,
    className: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.shape({
        base: PropTypes.string.isRequired,
        afterOpen: PropTypes.string.isRequired,
        beforeClose: PropTypes.string.isRequired
      })
    ]),
    overlayClassName: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.shape({
        base: PropTypes.string.isRequired,
        afterOpen: PropTypes.string.isRequired,
        beforeClose: PropTypes.string.isRequired
      })
    ]),
    appElement: PropTypes.instanceOf(SafeHTMLElement),
    onAfterOpen: PropTypes.func,
    onRequestClose: PropTypes.func,
    closeTimeoutMS: PropTypes.number,
    ariaHideApp: PropTypes.bool,
    shouldFocusAfterRender: PropTypes.bool,
    shouldCloseOnOverlayClick: PropTypes.bool,
    shouldReturnFocusAfterClose: PropTypes.bool,
    preventScroll: PropTypes.bool,
    parentSelector: PropTypes.func,
    aria: PropTypes.object,
    data: PropTypes.object,
    role: PropTypes.string,
    contentLabel: PropTypes.string,
    shouldCloseOnEsc: PropTypes.bool,
    overlayRef: PropTypes.func,
    contentRef: PropTypes.func
  };
  /* eslint-enable react/no-unused-prop-types */

  static defaultProps = {
    isOpen: false,
    portalClassName,
    bodyOpenClassName,
    role: "dialog",
    ariaHideApp: true,
    closeTimeoutMS: 0,
    shouldFocusAfterRender: true,
    shouldCloseOnEsc: true,
    shouldCloseOnOverlayClick: true,
    shouldReturnFocusAfterClose: true,
    preventScroll: false,
    parentSelector: () => document.body
  };

  static defaultStyles = {
    overlay: {
      position: "fixed",
      top: 0,
      left: 0,
      right: 0,
      bottom: 0,
      backgroundColor: "rgba(255, 255, 255, 0.75)"
    },
    content: {
      position: "absolute",
      top: "40px",
      left: "40px",
      right: "40px",
      bottom: "40px",
      border: "1px solid #ccc",
      background: "#fff",
      overflow: "auto",
      WebkitOverflowScrolling: "touch",
      borderRadius: "4px",
      outline: "none",
      padding: "20px"
    }
  };

  componentDidMount() {
    if (!canUseDOM) return;

    if (!isReact16) {
      this.node = document.createElement("div");
    }
    this.node.className = this.props.portalClassName;

    const parent = getParentElement(this.props.parentSelector);
    parent.appendChild(this.node);

    !isReact16 && this.renderPortal(this.props);
  }

  getSnapshotBeforeUpdate(prevProps) {
    const prevParent = getParentElement(prevProps.parentSelector);
    const nextParent = getParentElement(this.props.parentSelector);
    return { prevParent, nextParent };
  }

  componentDidUpdate(prevProps, _, snapshot) {
    if (!canUseDOM) return;
    const { isOpen, portalClassName } = this.props;

    if (prevProps.portalClassName !== portalClassName) {
      this.node.className = portalClassName;
    }

    const { prevParent, nextParent } = snapshot;
    if (nextParent !== prevParent) {
      prevParent.removeChild(this.node);
      nextParent.appendChild(this.node);
    }

    // Stop unnecessary renders if modal is remaining closed
    if (!prevProps.isOpen && !isOpen) return;

    !isReact16 && this.renderPortal(this.props);
  }

  componentWillUnmount() {
    if (!canUseDOM || !this.node || !this.portal) return;

    const state = this.portal.state;
    const now = Date.now();
    const closesAt =
      state.isOpen &&
      this.props.closeTimeoutMS &&
      (state.closesAt || now + this.props.closeTimeoutMS);

    if (closesAt) {
      if (!state.beforeClose) {
        this.portal.closeWithTimeout();
      }

      setTimeout(this.removePortal, closesAt - now);
    } else {
      this.removePortal();
    }
  }

  removePortal = () => {
    !isReact16 && ReactDOM.unmountComponentAtNode(this.node);
    const parent = getParentElement(this.props.parentSelector);
    if (parent && parent.contains(this.node)) {
      parent.removeChild(this.node);
    } else {
      // eslint-disable-next-line no-console
      console.warn(
        'React-Modal: "parentSelector" prop did not returned any DOM ' +
          "element. Make sure that the parent element is unmounted to " +
          "avoid any memory leaks."
      );
    }
  };

  portalRef = ref => {
    this.portal = ref;
  };

  renderPortal = props => {
    const createPortal = getCreatePortal();
    const portal = createPortal(
      this,
      <ModalPortal defaultStyles={Modal.defaultStyles} {...props} />,
      this.node
    );
    this.portalRef(portal);
  };

  render() {
    if (!canUseDOM || !isReact16) {
      return null;
    }

    if (!this.node && isReact16) {
      this.node = document.createElement("div");
    }

    const createPortal = getCreatePortal();
    return createPortal(
      <ModalPortal
        ref={this.portalRef}
        defaultStyles={Modal.defaultStyles}
        {...this.props}
      />,
      this.node
    );
  }
}

polyfill(Modal);

export default Modal;
<|endoftext|>


<commit_before>import React from 'react';

const Notif = ({ kind, componentClassName, actionLabel, onActionClick, id, message }) => {
  const handleActionClick = (ev) => {
    ev.preventDefault();

    if (!onActionClick) {
      return;
    }

    onActionClick(id);
  };

  return (
    <div className={`${componentClassName} ${componentClassName}--${kind}`}>
      <div className={`${componentClassName}__icon`} />
      <div className={`${componentClassName}__content`}>
        <span className={`${componentClassName}__message`}>{message}

      {actionLabel &&
        <span className={`${componentClassName}__action`}>
          <button onClick={handleActionClick}>{actionLabel}</button>

      }
      <div className={`${componentClassName}__close`} />

  );
};

Notif.defaultProps = {
  kind: 'info',
};

Notif.propTypes = {
  id: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.number,
  ]).isRequired,
  message: React.PropTypes.string.isRequired,
  kind: React.PropTypes.oneOf([
    'success',
    'info',
    'warning',
    'danger',
  ]).isRequired,
  componentClassName: React.PropTypes.string,
  onActionClick: React.PropTypes.func,
  actionLabel: React.PropTypes.string,
};

export default Notif;
<commit_msg>Allow HTML/React elements in message content<commit_after>import React from 'react';

const Notif = ({ kind, componentClassName, actionLabel, onActionClick, id, message }) => {
  const handleActionClick = (ev) => {
    ev.preventDefault();

    if (!onActionClick) {
      return;
    }

    onActionClick(id);
  };

  return (
    <div className={`${componentClassName} ${componentClassName}--${kind}`}>
      <div className={`${componentClassName}__icon`} />
      <div className={`${componentClassName}__content`}>
        <span className={`${componentClassName}__message`}>{message}

      {actionLabel &&
        <span className={`${componentClassName}__action`}>
          <button onClick={handleActionClick}>{actionLabel}</button>

      }
      <div className={`${componentClassName}__close`} />

  );
};

Notif.defaultProps = {
  kind: 'info',
};

Notif.propTypes = {
  id: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.number,
  ]).isRequired,
  message: React.PropTypes.node.isRequired,
  kind: React.PropTypes.oneOf([
    'success',
    'info',
    'warning',
    'danger',
  ]).isRequired,
  componentClassName: React.PropTypes.string,
  onActionClick: React.PropTypes.func,
  actionLabel: React.PropTypes.string,
};

export default Notif;
<|endoftext|>