<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: 'Matrix',

  properties: [
    {
      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',
      class: 'Simple'
    }
  ],

  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(m) {
      var a = this.a, b = this.b, c = this.c,
          d = this.d, e = this.e, f = this.f,
          g = this.g, h = this.h, i = this.i;

      this.a = a * m.a + b * m.d + c * m.g;
      this.b = a * m.b + b * m.e + c * m.h;
      this.c = a * m.c + b * m.f + c * m.i;

      this.d = d * m.a + e * m.d + f * m.g;
      this.e = d * m.b + e * m.e + f * m.h;
      this.f = d * m.c + e * m.f + f * m.i;

      this.g = g * m.a + h * m.d + i * m.g;
      this.h = g * m.b + h * m.e + i * m.h;
      this.i = g * m.c + h * m.f + i * m.i;

      return this;
    },

    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;
    }
  ]
});

// TODO: Perhaps these methods should be moved to matrix.
foam.CLASS({
  package: 'foam.graphics',
  name: 'Transform',

  requires: [
    'foam.graphics.Matrix'
  ],

  properties: [
    {
      name: 'matrix',
      factory: function() {
        return this.Matrix.create();
      },
    },
    {
      name: 'm2',
      factory: function() {
        return this.Matrix.create();
      }
    }
  ],

  methods: [
    function reset() {
      this.matrix.initArgs();
      return this;
    },

    function affine(m) {
      this.matrix.mul(m);
      return this;
    },

    function translate(dx, dy) {
      if ( ! dx && ! dy ) return;
      //      var m = this.Matrix.create()
      var m = this.m2;
      m.a = 1; m.b = 0; m.c = dx;
      m.d = 0; m.e = 1; m.f = dy;
      m.g = 0; m.h = 0; m.i = 1;
      this.matrix.mul(m);
    },

    function skew(x, y) {
      if ( ! x && ! y ) return;
      //      var m = this.Matrix.create();
      var m = this.m2;
      m.a = 1; m.b = x; m.c = 0;
      m.d = y; m.e = 1; m.f = 0;
      m.g = 0; m.h = 0; m.i = 1;
      this.matrix.mul(m);
    },

    function scale(x, y) {
      if ( x === 1 && y === 1 ) return;
      //      var m = this.Matrix.create();
      var m = this.m2;
      m.a = x; m.b = 0; m.c = 0;
      m.d = 0; m.e = y; m.f = 0;
      m.g = 0; m.h = 0; m.i = 1;
      this.matrix.mul(m);
    },

    function rotate(a) {
      if ( ! a ) return;
      //      var m = this.Matrix.create();
      var m = this.m2;
      m.a = Math.cos(a);  m.b = Math.sin(a); m.c = 0;
      m.d = -Math.sin(a); m.e = Math.cos(a); m.f = 0;
      m.g = 0;            m.h = 0;           m.i = 1;
      this.matrix.mul(m);
    }
  ]
});

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() {
        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.invalidated.pub();
      }.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.matrix;
      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]);
    },

    function paintSelf(x) {},

    // 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: [
    function onChildUpdate() { this.invalidated.pub(); }
  ]
});

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;
      if ( c.border    ) r += c.arcWidth-2;
      return foam.math.distance(this.x-c.x, this.y-c.y) <= 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() {
        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;
    }
  ]
});
<commit_msg>CView wasn't listeneing to children.<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: 'Matrix',

  properties: [
    {
      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',
      class: 'Simple'
    }
  ],

  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(m) {
      var a = this.a, b = this.b, c = this.c,
          d = this.d, e = this.e, f = this.f,
          g = this.g, h = this.h, i = this.i;

      this.a = a * m.a + b * m.d + c * m.g;
      this.b = a * m.b + b * m.e + c * m.h;
      this.c = a * m.c + b * m.f + c * m.i;

      this.d = d * m.a + e * m.d + f * m.g;
      this.e = d * m.b + e * m.e + f * m.h;
      this.f = d * m.c + e * m.f + f * m.i;

      this.g = g * m.a + h * m.d + i * m.g;
      this.h = g * m.b + h * m.e + i * m.h;
      this.i = g * m.c + h * m.f + i * m.i;

      return this;
    },

    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;
    }
  ]
});

// TODO: Perhaps these methods should be moved to matrix.
foam.CLASS({
  package: 'foam.graphics',
  name: 'Transform',

  requires: [
    'foam.graphics.Matrix'
  ],

  properties: [
    {
      name: 'matrix',
      factory: function() {
        return this.Matrix.create();
      },
    },
    {
      name: 'm2',
      factory: function() {
        return this.Matrix.create();
      }
    }
  ],

  methods: [
    function reset() {
      this.matrix.initArgs();
      return this;
    },

    function affine(m) {
      this.matrix.mul(m);
      return this;
    },

    function translate(dx, dy) {
      if ( ! dx && ! dy ) return;
      //      var m = this.Matrix.create()
      var m = this.m2;
      m.a = 1; m.b = 0; m.c = dx;
      m.d = 0; m.e = 1; m.f = dy;
      m.g = 0; m.h = 0; m.i = 1;
      this.matrix.mul(m);
    },

    function skew(x, y) {
      if ( ! x && ! y ) return;
      //      var m = this.Matrix.create();
      var m = this.m2;
      m.a = 1; m.b = x; m.c = 0;
      m.d = y; m.e = 1; m.f = 0;
      m.g = 0; m.h = 0; m.i = 1;
      this.matrix.mul(m);
    },

    function scale(x, y) {
      if ( x === 1 && y === 1 ) return;
      //      var m = this.Matrix.create();
      var m = this.m2;
      m.a = x; m.b = 0; m.c = 0;
      m.d = 0; m.e = y; m.f = 0;
      m.g = 0; m.h = 0; m.i = 1;
      this.matrix.mul(m);
    },

    function rotate(a) {
      if ( ! a ) return;
      //      var m = this.Matrix.create();
      var m = this.m2;
      m.a = Math.cos(a);  m.b = Math.sin(a); m.c = 0;
      m.d = -Math.sin(a); m.e = Math.cos(a); m.f = 0;
      m.g = 0;            m.h = 0;           m.i = 1;
      this.matrix.mul(m);
    }
  ]
});

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() {
        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.invalidated.pub();
      }.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.matrix;
      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) {},

    // 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: [
    function onChildUpdate() { this.invalidated.pub(); }
  ]
});

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;
      if ( c.border    ) r += c.arcWidth-2;
      return foam.math.distance(this.x-c.x, this.y-c.y) <= 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() {
        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;
    }
  ]
});
<|endoftext|>


<commit_before>import $ from 'jquery';
import ModalComponent from 'ghost-admin/components/modal-base';
import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard';
import {alias, reads} from '@ember/object/computed';
import {computed} from '@ember/object';
import {htmlSafe} from '@ember/string';
import {run} from '@ember/runloop';
import {inject as service} from '@ember/service';
import {task, timeout} from 'ember-concurrency';
const ICON_EXTENSIONS = ['gif', 'jpg', 'jpeg', 'png', 'svg'];

const ICON_MAPPING = [
    {
        icon: 'portal-icon-1',
        value: 'icon-1'
    },
    {
        icon: 'portal-icon-2',
        value: 'icon-2'
    },
    {
        icon: 'portal-icon-3',
        value: 'icon-3'
    },
    {
        icon: 'portal-icon-4',
        value: 'icon-4'
    },
    {
        icon: 'portal-icon-5',
        value: 'icon-5'
    }
];

export default ModalComponent.extend({
    settings: service(),
    membersUtils: service(),
    config: service(),
    page: 'signup',
    iconExtensions: null,
    defaultButtonIcons: null,
    isShowModalLink: true,
    customIcon: null,
    showLinksPage: false,
    showLeaveSettingsModal: false,
    confirm() {},

    allowSelfSignup: alias('settings.membersAllowFreeSignup'),

    isStripeConfigured: reads('membersUtils.isStripeEnabled'),

    buttonIcon: computed('settings.portalButtonIcon', function () {
        const defaultIconKeys = this.defaultButtonIcons.map(buttonIcon => buttonIcon.value);
        return (this.settings.get('portalButtonIcon') || defaultIconKeys[0]);
    }),

    backgroundStyle: computed('settings.accentColor', function () {
        let color = this.get('settings.accentColor') || '#ffffff';
        return htmlSafe(`background-color: ${color}`);
    }),

    colorPickerValue: computed('settings.accentColor', function () {
        return this.get('settings.accentColor') || '#ffffff';
    }),

    accentColor: computed('settings.accentColor', function () {
        let color = this.get('settings.accentColor');
        if (color && color[0] === '#') {
            return color.slice(1);
        }
        return color;
    }),

    showModalLinkOrAttribute: computed('isShowModalLink', function () {
        if (this.isShowModalLink) {
            return `#/portal`;
        }
        return `data-portal`;
    }),

    portalPreviewUrl: computed('buttonIcon', 'page', 'isFreeChecked', 'isMonthlyChecked', 'isYearlyChecked', 'settings.{portalName,portalButton,portalButtonSignupText,portalButtonStyle,accentColor}', function () {
        const baseUrl = this.config.get('blogUrl');
        const portalBase = '/#/portal/preview';
        const settingsParam = new URLSearchParams();
        settingsParam.append('button', this.settings.get('portalButton'));
        settingsParam.append('name', this.settings.get('portalName'));
        settingsParam.append('isFree', this.isFreeChecked);
        settingsParam.append('isMonthly', this.isMonthlyChecked);
        settingsParam.append('isYearly', this.isYearlyChecked);
        settingsParam.append('page', this.page);
        if (this.buttonIcon) {
            settingsParam.append('buttonIcon', encodeURIComponent(this.buttonIcon));
        }
        settingsParam.append('signupButtonText', encodeURIComponent(this.settings.get('portalButtonSignupText')));
        if (this.settings.get('accentColor') === '' || this.settings.get('accentColor')) {
            settingsParam.append('accentColor', encodeURIComponent(`${this.settings.get('accentColor')}`));
        }
        if (this.settings.get('portalButtonStyle')) {
            settingsParam.append('buttonStyle', encodeURIComponent(this.settings.get('portalButtonStyle')));
        }
        return `${baseUrl}${portalBase}?${settingsParam.toString()}`;
    }),

    showIconSetting: computed('selectedButtonStyle', function () {
        const selectedButtonStyle = this.get('selectedButtonStyle.name') || '';
        return selectedButtonStyle.includes('icon');
    }),

    showButtonTextSetting: computed('selectedButtonStyle', function () {
        const selectedButtonStyle = this.get('selectedButtonStyle.name') || '';
        return selectedButtonStyle.includes('text');
    }),

    isFreeChecked: computed('settings.portalPlans.[]', 'allowSelfSignup', function () {
        const allowedPlans = this.settings.get('portalPlans') || [];
        return (this.allowSelfSignup && allowedPlans.includes('free'));
    }),

    isMonthlyChecked: computed('settings.portalPlans.[]', 'isStripeConfigured', function () {
        const allowedPlans = this.settings.get('portalPlans') || [];
        return (this.isStripeConfigured && allowedPlans.includes('monthly'));
    }),

    isYearlyChecked: computed('settings.portalPlans.[]', 'isStripeConfigured', function () {
        const allowedPlans = this.settings.get('portalPlans') || [];
        return (this.isStripeConfigured && allowedPlans.includes('yearly'));
    }),

    selectedButtonStyle: computed('settings.portalButtonStyle', function () {
        return this.buttonStyleOptions.find((buttonStyle) => {
            return (buttonStyle.name === this.settings.get('portalButtonStyle'));
        });
    }),

    init() {
        this._super(...arguments);
        this.set('hidePreviewFrame', true);
        this.buttonStyleOptions = [
            {name: 'icon-and-text', label: 'Icon and text'},
            {name: 'icon-only', label: 'Icon only'},
            {name: 'text-only', label: 'Text only'}
        ];
        this.defaultButtonIcons = ICON_MAPPING;
        this.iconExtensions = ICON_EXTENSIONS;
        const portalButtonIcon = this.settings.get('portalButtonIcon') || '';
        const defaultIconKeys = this.defaultButtonIcons.map(buttonIcon => buttonIcon.value);
        if (portalButtonIcon && !defaultIconKeys.includes(portalButtonIcon)) {
            this.set('customIcon', this.settings.get('portalButtonIcon'));
        }
        this.siteUrl = this.config.get('blogUrl');
    },

    didInsertElement() {
        this._super(...arguments);
        run.later(this, function () {
            this.set('hidePreviewFrame', false);
        }, 1200);
    },

    actions: {
        toggleFreePlan(isChecked) {
            this.updateAllowedPlan('free', isChecked);
        },
        toggleMonthlyPlan(isChecked) {
            this.updateAllowedPlan('monthly', isChecked);
        },
        toggleYearlyPlan(isChecked) {
            this.updateAllowedPlan('yearly', isChecked);
        },
        togglePortalButton(showButton) {
            this.settings.set('portalButton', showButton);
        },

        togglePortalName(showSignupName) {
            this.settings.set('portalName', showSignupName);
        },

        confirm() {
            return this.saveTask.perform();
        },

        isPlanSelected(plan) {
            const allowedPlans = this.settings.get('portalPlans');
            return allowedPlans.includes(plan);
        },

        switchPreviewPage(page) {
            if (page === 'links') {
                this.set('showLinksPage', true);
                this.set('page', '');
            } else {
                this.set('showLinksPage', false);
                this.set('page', page);
            }
        },

        updateAccentColor(color) {
            this._validateAccentColor(color);
        },

        validateAccentColor() {
            this._validateAccentColor(this.get('accentColor'));
        },
        setButtonStyle(buttonStyle) {
            this.settings.set('portalButtonStyle', buttonStyle.name);
        },
        setSignupButtonText(event) {
            this.settings.set('portalButtonSignupText', event.target.value);
        },
        /**
         * Fired after an image upload completes
         * @param  {string} property - Property name to be set on `this.settings`
         * @param  {UploadResult[]} results - Array of UploadResult objects
         * @return {string} The URL that was set on `this.settings.property`
         */
        imageUploaded(property, results) {
            if (results[0]) {
                this.set('customIcon', results[0].url);
                this.settings.set('portalButtonIcon', results[0].url);
            }
        },
        /**
         * Opens a file selection dialog - Triggered by "Upload Image" buttons,
         * searches for the hidden file input within the .gh-setting element
         * containing the clicked button then simulates a click
         * @param  {MouseEvent} event - MouseEvent fired by the button click
         */
        triggerFileDialog(event) {
            // simulate click to open file dialog
            // using jQuery because IE11 doesn't support MouseEvent
            $(event.target)
                .closest('.gh-setting-action')
                .find('input[type="file"]')
                .click();
        },

        deleteCustomIcon() {
            this.set('customIcon', null);
            const defaultIconKeys = ICON_MAPPING.map(buttonIcon => buttonIcon.value);
            this.settings.set('portalButtonIcon', defaultIconKeys[0]);
        },

        selectDefaultIcon(icon) {
            this.settings.set('portalButtonIcon', icon);
        },

        closeLeaveSettingsModal() {
            this.set('showLeaveSettingsModal', false);
        },

        leaveSettings() {
            this.closeModal();
        }
    },

    updateAllowedPlan(plan, isChecked) {
        const allowedPlans = this.settings.get('portalPlans') || [];

        if (!isChecked) {
            this.settings.set('portalPlans', allowedPlans.filter(p => p !== plan));
        } else {
            allowedPlans.push(plan);
            this.settings.set('portalPlans', [...allowedPlans]);
        }
    },

    _validateAccentColor(color) {
        let newColor = color;
        let oldColor = this.get('settings.accentColor');
        let errMessage = '';

        // reset errors and validation
        this.get('settings.errors').remove('accentColor');
        this.get('settings.hasValidated').removeObject('accentColor');

        if (newColor === '') {
            // Clear out the accent color
            run.schedule('afterRender', this, function () {
                this.settings.set('accentColor', '');
                this.set('accentColor', '');
            });
            return;
        }

        // accentColor will be null unless the user has input something
        if (!newColor) {
            newColor = oldColor;
        }

        if (newColor[0] !== '#') {
            newColor = `#${newColor}`;
        }

        if (newColor.match(/#[0-9A-Fa-f]{6}$/)) {
            this.set('settings.accentColor', '');
            run.schedule('afterRender', this, function () {
                this.set('settings.accentColor', newColor);
                this.set('accentColor', newColor.slice(1));
            });
        } else {
            errMessage = 'The color should be in valid hex format';
            this.get('settings.errors').add('accentColor', errMessage);
            this.get('settings.hasValidated').pushObject('accentColor');
            return;
        }
    },

    copyLinkOrAttribute: task(function* () {
        copyTextToClipboard(this.showModalLinkOrAttribute);
        yield timeout(this.isTesting ? 50 : 3000);
    }),

    saveTask: task(function* () {
        yield this.settings.save();
        this.closeModal();
    }).drop()
});
<commit_msg>Fixed 'null' portal button text<commit_after>import $ from 'jquery';
import ModalComponent from 'ghost-admin/components/modal-base';
import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard';
import {alias, reads} from '@ember/object/computed';
import {computed} from '@ember/object';
import {htmlSafe} from '@ember/string';
import {run} from '@ember/runloop';
import {inject as service} from '@ember/service';
import {task, timeout} from 'ember-concurrency';
const ICON_EXTENSIONS = ['gif', 'jpg', 'jpeg', 'png', 'svg'];

const ICON_MAPPING = [
    {
        icon: 'portal-icon-1',
        value: 'icon-1'
    },
    {
        icon: 'portal-icon-2',
        value: 'icon-2'
    },
    {
        icon: 'portal-icon-3',
        value: 'icon-3'
    },
    {
        icon: 'portal-icon-4',
        value: 'icon-4'
    },
    {
        icon: 'portal-icon-5',
        value: 'icon-5'
    }
];

export default ModalComponent.extend({
    settings: service(),
    membersUtils: service(),
    config: service(),
    page: 'signup',
    iconExtensions: null,
    defaultButtonIcons: null,
    isShowModalLink: true,
    customIcon: null,
    showLinksPage: false,
    showLeaveSettingsModal: false,
    confirm() {},

    allowSelfSignup: alias('settings.membersAllowFreeSignup'),

    isStripeConfigured: reads('membersUtils.isStripeEnabled'),

    buttonIcon: computed('settings.portalButtonIcon', function () {
        const defaultIconKeys = this.defaultButtonIcons.map(buttonIcon => buttonIcon.value);
        return (this.settings.get('portalButtonIcon') || defaultIconKeys[0]);
    }),

    backgroundStyle: computed('settings.accentColor', function () {
        let color = this.get('settings.accentColor') || '#ffffff';
        return htmlSafe(`background-color: ${color}`);
    }),

    colorPickerValue: computed('settings.accentColor', function () {
        return this.get('settings.accentColor') || '#ffffff';
    }),

    accentColor: computed('settings.accentColor', function () {
        let color = this.get('settings.accentColor');
        if (color && color[0] === '#') {
            return color.slice(1);
        }
        return color;
    }),

    showModalLinkOrAttribute: computed('isShowModalLink', function () {
        if (this.isShowModalLink) {
            return `#/portal`;
        }
        return `data-portal`;
    }),

    portalPreviewUrl: computed('buttonIcon', 'page', 'isFreeChecked', 'isMonthlyChecked', 'isYearlyChecked', 'settings.{portalName,portalButton,portalButtonSignupText,portalButtonStyle,accentColor}', function () {
        const baseUrl = this.config.get('blogUrl');
        const portalBase = '/#/portal/preview';
        const settingsParam = new URLSearchParams();
        const signupButtonText = this.settings.get('portalButtonSignupText') || '';
        settingsParam.append('button', this.settings.get('portalButton'));
        settingsParam.append('name', this.settings.get('portalName'));
        settingsParam.append('isFree', this.isFreeChecked);
        settingsParam.append('isMonthly', this.isMonthlyChecked);
        settingsParam.append('isYearly', this.isYearlyChecked);
        settingsParam.append('page', this.page);
        if (this.buttonIcon) {
            settingsParam.append('buttonIcon', encodeURIComponent(this.buttonIcon));
        }
        settingsParam.append('signupButtonText', encodeURIComponent(signupButtonText));
        if (this.settings.get('accentColor') === '' || this.settings.get('accentColor')) {
            settingsParam.append('accentColor', encodeURIComponent(`${this.settings.get('accentColor')}`));
        }
        if (this.settings.get('portalButtonStyle')) {
            settingsParam.append('buttonStyle', encodeURIComponent(this.settings.get('portalButtonStyle')));
        }
        return `${baseUrl}${portalBase}?${settingsParam.toString()}`;
    }),

    showIconSetting: computed('selectedButtonStyle', function () {
        const selectedButtonStyle = this.get('selectedButtonStyle.name') || '';
        return selectedButtonStyle.includes('icon');
    }),

    showButtonTextSetting: computed('selectedButtonStyle', function () {
        const selectedButtonStyle = this.get('selectedButtonStyle.name') || '';
        return selectedButtonStyle.includes('text');
    }),

    isFreeChecked: computed('settings.portalPlans.[]', 'allowSelfSignup', function () {
        const allowedPlans = this.settings.get('portalPlans') || [];
        return (this.allowSelfSignup && allowedPlans.includes('free'));
    }),

    isMonthlyChecked: computed('settings.portalPlans.[]', 'isStripeConfigured', function () {
        const allowedPlans = this.settings.get('portalPlans') || [];
        return (this.isStripeConfigured && allowedPlans.includes('monthly'));
    }),

    isYearlyChecked: computed('settings.portalPlans.[]', 'isStripeConfigured', function () {
        const allowedPlans = this.settings.get('portalPlans') || [];
        return (this.isStripeConfigured && allowedPlans.includes('yearly'));
    }),

    selectedButtonStyle: computed('settings.portalButtonStyle', function () {
        return this.buttonStyleOptions.find((buttonStyle) => {
            return (buttonStyle.name === this.settings.get('portalButtonStyle'));
        });
    }),

    init() {
        this._super(...arguments);
        this.set('hidePreviewFrame', true);
        this.buttonStyleOptions = [
            {name: 'icon-and-text', label: 'Icon and text'},
            {name: 'icon-only', label: 'Icon only'},
            {name: 'text-only', label: 'Text only'}
        ];
        this.defaultButtonIcons = ICON_MAPPING;
        this.iconExtensions = ICON_EXTENSIONS;
        const portalButtonIcon = this.settings.get('portalButtonIcon') || '';
        const defaultIconKeys = this.defaultButtonIcons.map(buttonIcon => buttonIcon.value);
        if (portalButtonIcon && !defaultIconKeys.includes(portalButtonIcon)) {
            this.set('customIcon', this.settings.get('portalButtonIcon'));
        }
        this.siteUrl = this.config.get('blogUrl');
    },

    didInsertElement() {
        this._super(...arguments);
        run.later(this, function () {
            this.set('hidePreviewFrame', false);
        }, 1200);
    },

    actions: {
        toggleFreePlan(isChecked) {
            this.updateAllowedPlan('free', isChecked);
        },
        toggleMonthlyPlan(isChecked) {
            this.updateAllowedPlan('monthly', isChecked);
        },
        toggleYearlyPlan(isChecked) {
            this.updateAllowedPlan('yearly', isChecked);
        },
        togglePortalButton(showButton) {
            this.settings.set('portalButton', showButton);
        },

        togglePortalName(showSignupName) {
            this.settings.set('portalName', showSignupName);
        },

        confirm() {
            return this.saveTask.perform();
        },

        isPlanSelected(plan) {
            const allowedPlans = this.settings.get('portalPlans');
            return allowedPlans.includes(plan);
        },

        switchPreviewPage(page) {
            if (page === 'links') {
                this.set('showLinksPage', true);
                this.set('page', '');
            } else {
                this.set('showLinksPage', false);
                this.set('page', page);
            }
        },

        updateAccentColor(color) {
            this._validateAccentColor(color);
        },

        validateAccentColor() {
            this._validateAccentColor(this.get('accentColor'));
        },
        setButtonStyle(buttonStyle) {
            this.settings.set('portalButtonStyle', buttonStyle.name);
        },
        setSignupButtonText(event) {
            this.settings.set('portalButtonSignupText', event.target.value);
        },
        /**
         * Fired after an image upload completes
         * @param  {string} property - Property name to be set on `this.settings`
         * @param  {UploadResult[]} results - Array of UploadResult objects
         * @return {string} The URL that was set on `this.settings.property`
         */
        imageUploaded(property, results) {
            if (results[0]) {
                this.set('customIcon', results[0].url);
                this.settings.set('portalButtonIcon', results[0].url);
            }
        },
        /**
         * Opens a file selection dialog - Triggered by "Upload Image" buttons,
         * searches for the hidden file input within the .gh-setting element
         * containing the clicked button then simulates a click
         * @param  {MouseEvent} event - MouseEvent fired by the button click
         */
        triggerFileDialog(event) {
            // simulate click to open file dialog
            // using jQuery because IE11 doesn't support MouseEvent
            $(event.target)
                .closest('.gh-setting-action')
                .find('input[type="file"]')
                .click();
        },

        deleteCustomIcon() {
            this.set('customIcon', null);
            const defaultIconKeys = ICON_MAPPING.map(buttonIcon => buttonIcon.value);
            this.settings.set('portalButtonIcon', defaultIconKeys[0]);
        },

        selectDefaultIcon(icon) {
            this.settings.set('portalButtonIcon', icon);
        },

        closeLeaveSettingsModal() {
            this.set('showLeaveSettingsModal', false);
        },

        leaveSettings() {
            this.closeModal();
        }
    },

    updateAllowedPlan(plan, isChecked) {
        const allowedPlans = this.settings.get('portalPlans') || [];

        if (!isChecked) {
            this.settings.set('portalPlans', allowedPlans.filter(p => p !== plan));
        } else {
            allowedPlans.push(plan);
            this.settings.set('portalPlans', [...allowedPlans]);
        }
    },

    _validateAccentColor(color) {
        let newColor = color;
        let oldColor = this.get('settings.accentColor');
        let errMessage = '';

        // reset errors and validation
        this.get('settings.errors').remove('accentColor');
        this.get('settings.hasValidated').removeObject('accentColor');

        if (newColor === '') {
            // Clear out the accent color
            run.schedule('afterRender', this, function () {
                this.settings.set('accentColor', '');
                this.set('accentColor', '');
            });
            return;
        }

        // accentColor will be null unless the user has input something
        if (!newColor) {
            newColor = oldColor;
        }

        if (newColor[0] !== '#') {
            newColor = `#${newColor}`;
        }

        if (newColor.match(/#[0-9A-Fa-f]{6}$/)) {
            this.set('settings.accentColor', '');
            run.schedule('afterRender', this, function () {
                this.set('settings.accentColor', newColor);
                this.set('accentColor', newColor.slice(1));
            });
        } else {
            errMessage = 'The color should be in valid hex format';
            this.get('settings.errors').add('accentColor', errMessage);
            this.get('settings.hasValidated').pushObject('accentColor');
            return;
        }
    },

    copyLinkOrAttribute: task(function* () {
        copyTextToClipboard(this.showModalLinkOrAttribute);
        yield timeout(this.isTesting ? 50 : 3000);
    }),

    saveTask: task(function* () {
        yield this.settings.save();
        this.closeModal();
    }).drop()
});
<|endoftext|>


<commit_before>import React from "react";
import AlertPanel from "#SRC/js/components/AlertPanel";
import AlertPanelHeader from "#SRC/js/components/AlertPanelHeader";

const ServiceNoEndpointPanel = props => {
  const { serviceId, onClick } = props;

  return (
    <AlertPanel>
      <AlertPanelHeader>No Endpoints</AlertPanelHeader>

        There are no endpoints currently configured for
        {" "}
        {serviceId}.
        {" "}
        You can edit the configuration to add service endpoints.

        <button className="button" onClick={onClick}>
          Edit Configuration
        </button>

    </AlertPanel>
  );
};

module.exports = ServiceNoEndpointPanel;
<commit_msg>fix(ServiceEndpointsTab): add proptypes to no-endpoint-tab panel component<commit_after>import React from "react";
import AlertPanel from "#SRC/js/components/AlertPanel";
import AlertPanelHeader from "#SRC/js/components/AlertPanelHeader";

const ServiceNoEndpointPanel = props => {
  const { serviceId, onClick } = props;

  return (
    <AlertPanel>
      <AlertPanelHeader>No Endpoints</AlertPanelHeader>

        There are no endpoints currently configured for
        {" "}
        {serviceId}.
        {" "}
        You can edit the configuration to add service endpoints.

        <button className="button" onClick={onClick}>
          Edit Configuration
        </button>

    </AlertPanel>
  );
};

ServiceNoEndpointPanel.propTypes = {
  onClick: React.PropTypes.func.isRequired,
  serviceId: React.PropTypes.string.isRequired
};

module.exports = ServiceNoEndpointPanel;
<|endoftext|>


<commit_before>(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
rogoto = require('rogoto-js');

},{"rogoto-js":2}],2:[function(require,module,exports){
function RogotoParserException (message) {
    this.message = message;
    this.name = "RogotoParserException";
}

function RogotoParser () {
    this.codeToExecute = [];
    this.penState = 'up';
}

RogotoParser.prototype.parse = function(logoCode) {
    if (!logoCode) {
        throw new RogotoParserException("You need to pass in a string of code");
    }

    var codeRegex = /pendown|pd|penup|pu|forward \d+|fd \d+|back \d+|bk \d+|right \d+|rt \d+|left \d+|lt \d+/;
    commands = logoCode.split('\n');
    for (var i = 0; i < commands.length; i++) {
      var match = codeRegex.exec(commands[i]);
      if (!match) {
        throw new RogotoParserException("You need to pass in valid syntax");
      }
      var cmd = match[0].split(' ');
      switch (cmd[0]) {
        case 'pendown':
        case 'pd':
          this.codeToExecute.push('pendown');
          this.penState = 'down';
          break;
        case 'penup':
        case 'pu':
          this.codeToExecute.push('penup');
          this.penState = 'up';
          break;
        case 'forward':
        case 'fd':
          var command = match[0].split(' ')[0] == 'forward'? match[0] : 'forward ' + cmd[1];
          this.codeToExecute.push(command);
          break;
        case 'back':
        case 'bk':
          var command = match[0].split(' ')[0] == 'back'? match[0] : 'back ' + cmd[1];
          this.codeToExecute.push(command);
          break;
        case 'right':
        case 'rt':
          var command = match[0].split(' ')[0] == 'right'? match[0] : 'right ' + cmd[1];
          this.codeToExecute.push(command);
          break;
        case 'left':
        case 'lt':
          var command = match[0].split(' ')[0] == 'left'? match[0] : 'left ' + cmd[1];
          this.codeToExecute.push(command);
          break;
      }
    };
    return this.codeToExecute;
};

RogotoParser.prototype.clean = function() {
    this.codeToExecute = [];
};

module.exports = RogotoParser;
},{}]},{},[1])<commit_msg>updating bundle to latest Rogoto-js version<commit_after>(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({},{},[])<|endoftext|>


<commit_before>/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
module("XHR Interception");

(function() {

    var inj = Graphene.xhrInterception;
    var originalXhr = window.XMLHttpRequest;

    QUnit.testStart = function() {
        window.XMLHttpRequest = originalXhr;
        inj.uninstall();
    };

    function replaceXHRPrototype(methods) {
        var instances = [];
        var Replacement = function() {
            instances.push(this);
        };
        Replacement.prototype = methods;
        window.XMLHttpRequest = Replacement;
        return instances;
    }

    // TESTS

    test("accessor object not null", function() {
        ok(inj);
    });

    test("test injection", function() {
        // given
        ok(inj.install);
        var backup = window.XMLHttpRequest;

        // when
        inj.install();

        // then
        notStrictEqual(window.XMLHttpRequest, backup);
        ok(window.XMLHttpRequest);
        ok(window.XMLHttpRequest.prototype.abort);
    });

    test("test initial values", function() {
        // given
        ok(inj.install);
        var backup = window.XMLHttpRequest;

        // when
        inj.install();

        // then
        var xhr = new window.XMLHttpRequest();
        equal(xhr.readyState, 0);
        equal(xhr.response, "");
        equal(xhr.responseText, "");
        equal(xhr.responseType, "");
        equal(xhr.responseXML, null);
        equal(xhr.status, 0);
        equal(xhr.statusText, "");
    });

    test("test xhr wrapper delegation (for abort)", function() {
        // given
        var aborted = false;
        replaceXHRPrototype({
            abort : function() {
                aborted = true;
            }
        });

        // when
        inj.install();
        var xhr = new window.XMLHttpRequest();
        xhr.abort();

        // then
        ok(aborted);
    });

    test("test xhr interception (for abort)", function() {
        // given
        var abortInvoked = false;
        var interceptor1Started = false;
        var interceptor2Started = false;
        var interceptor1Finished = false;
        var interceptor2Finished = false;

        replaceXHRPrototype({
            abort : function() {
                abortInvoked = true;
                return "returnValue";
            }
        });

        // interceptor 1
        inj.onAbort(function(ctx, args) {
            interceptor1Started = true;

            ok(!abortInvoked);
            ok(!interceptor2Started);
            ok(!interceptor2Finished);

            var result = ctx.proceed(args);

            ok(abortInvoked);
            ok(interceptor2Started);
            ok(interceptor2Finished);

            interceptor1Finished = true;
            return result;
        });
        // interceptor 2
        inj.onAbort(function(ctx, args) {
            interceptor2Started = true;

            ok(!abortInvoked);
            ok(interceptor1Started);
            ok(!interceptor1Finished);

            var result = ctx.proceed(args);

            ok(abortInvoked);
            ok(interceptor1Started);
            ok(!interceptor1Finished);

            interceptor2Finished = true;

            return result;
        });

        // when
        inj.install();
        var xhr = new window.XMLHttpRequest();
        xhr.abort();

        // then
        ok(abortInvoked);
        ok(interceptor1Finished);
        ok(interceptor2Finished);
    });

    test("test xhr wrapper delegation (for onreadystatechange)", function() {
        // given
        var statechanged = false;
        var instances = replaceXHRPrototype();

        // when
        inj.install();
        var xhr = new window.XMLHttpRequest();
        xhr.onreadystatechange = function() {
            statechanged = true;
        }
        ok(instances[0]);
        instances[0].onreadystatechange();

        // then
        ok(statechanged);
    });

    test("test xhr interception (for onreadystatechange)", function() {
        // given
        var statechanged = false;
        var interceptor1Started = false;
        var interceptor2Started = false;
        var interceptor1Finished = false;
        var interceptor2Finished = false;

        var instances = replaceXHRPrototype();

        // interceptor 1
        inj.onreadystatechange(function(ctx, args) {
            interceptor1Started = true;

            ok(!statechanged);
            ok(!interceptor2Started);
            ok(!interceptor2Finished);

            var result = ctx.proceed(args);

            ok(statechanged);
            ok(interceptor2Started);
            ok(interceptor2Finished);

            interceptor1Finished = true;
            return result;
        });
        // interceptor 2
        inj.onreadystatechange(function(ctx, args) {
            interceptor2Started = true;

            ok(!statechanged);
            ok(interceptor1Started);
            ok(!interceptor1Finished);

            var result = ctx.proceed(args);

            ok(statechanged);
            ok(interceptor1Started);
            ok(!interceptor1Finished);

            interceptor2Finished = true;

            return result;
        });

        // when
        inj.install();
        var xhr = new window.XMLHttpRequest();
        xhr.onreadystatechange = function() {
            statechanged = true;
        }
        ok(instances[0]);
        instances[0].onreadystatechange();

        // then
        ok(statechanged);
        ok(interceptor1Finished);
        ok(interceptor2Finished);
    });

    test("test readystatechange", function() {
        // given
        var readyState = 0;
        var instances = replaceXHRPrototype();

        // when
        inj.install();
        var xhr = new window.XMLHttpRequest();

        var instance = instances[0];
        ok(instance);

        xhr.onreadystatechange = function(request) {
            ok(request.readyState == readyState);
            if (readyState < 4) {
                ok(request.responseText === "");
                ok(request.responseXML === null);
                ok(request.status === 0);
                ok(request.statusText === "");
            } else {
                equal(request.responseText, "responseText")
                equal(request.responseXML, "responseXML")
                equal(request.status, 200)
                equal(request.statusText, "statusText")
            }
            readyState += 1;
        }

        instance.readyState = 0;
        instance.onreadystatechange();

        instance.readyState = 1;
        instance.onreadystatechange();

        instance.readyState = 2;
        instance.onreadystatechange();

        instance.readyState = 3;
        instance.onreadystatechange();

        instance.readyState = 4;
        instance.responseText = "responseText";
        instance.responseXML = "responseXML";
        instance.status = 200;
        instance.statusText = "statusText";
        instance.onreadystatechange();

        // then
        equal(readyState, 5);
    })
})();<commit_msg>ARQGRA-344: fix xhrInterception.js test<commit_after>/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
module("XHR Interception");

(function() {

    var inj = Graphene.xhrInterception;
    var originalXhr = window.XMLHttpRequest;

    QUnit.testStart = function() {
        window.XMLHttpRequest = originalXhr;
        inj.uninstall();
    };

    function replaceXHRPrototype(methods) {
        var instances = [];
        var Replacement = function() {
            instances.push(this);
        };
        Replacement.prototype = methods;
        window.XMLHttpRequest = Replacement;
        return instances;
    }

    // TESTS

    test("accessor object not null", function() {
        ok(inj);
    });

    test("test injection", function() {
        // given
        ok(inj.install);
        var backup = window.XMLHttpRequest;

        // when
        inj.install();

        // then
        notStrictEqual(window.XMLHttpRequest, backup);
        ok(window.XMLHttpRequest);
        ok(window.XMLHttpRequest.prototype.abort);
    });

    test("test initial values", function() {
        // given
        ok(inj.install);
        var backup = window.XMLHttpRequest;

        // when
        inj.install();

        // then
        var xhr = new window.XMLHttpRequest();
        equal(xhr.readyState, 0);
        equal(xhr.response, "");
        equal(xhr.responseText, "");
        equal(xhr.responseType, "");
        equal(xhr.responseXML, null);
        equal(xhr.status, 0);
        equal(xhr.statusText, "");
    });

    test("test xhr wrapper delegation (for abort)", function() {
        // given
        var aborted = false;
        replaceXHRPrototype({
            abort : function() {
                aborted = true;
            }
        });

        // when
        inj.install();
        var xhr = new window.XMLHttpRequest();
        xhr.abort();

        // then
        ok(aborted);
    });

    test("test xhr interception (for abort)", function() {
        // given
        var abortInvoked = false;
        var interceptor1Started = false;
        var interceptor2Started = false;
        var interceptor1Finished = false;
        var interceptor2Finished = false;

        replaceXHRPrototype({
            abort : function() {
                abortInvoked = true;
                return "returnValue";
            }
        });

        // interceptor 1
        inj.onAbort(function(ctx, args) {
            interceptor1Started = true;

            ok(!abortInvoked);
            ok(!interceptor2Started);
            ok(!interceptor2Finished);

            var result = ctx.proceed(args);

            ok(abortInvoked);
            ok(interceptor2Started);
            ok(interceptor2Finished);

            interceptor1Finished = true;
            return result;
        });
        // interceptor 2
        inj.onAbort(function(ctx, args) {
            interceptor2Started = true;

            ok(!abortInvoked);
            ok(interceptor1Started);
            ok(!interceptor1Finished);

            var result = ctx.proceed(args);

            ok(abortInvoked);
            ok(interceptor1Started);
            ok(!interceptor1Finished);

            interceptor2Finished = true;

            return result;
        });

        // when
        inj.install();
        var xhr = new window.XMLHttpRequest();
        xhr.abort();

        // then
        ok(abortInvoked);
        ok(interceptor1Finished);
        ok(interceptor2Finished);
    });

    test("test xhr wrapper delegation (for onreadystatechange)", function() {
        // given
        var statechanged = false;
        var instances = replaceXHRPrototype();

        // when
        inj.install();
        var xhr = new window.XMLHttpRequest();
        xhr.onreadystatechange = function() {
            statechanged = true;
        }
        ok(instances[0]);
        instances[0].onreadystatechange();

        // then
        ok(statechanged);
    });

    test("test xhr interception (for onreadystatechange)", function() {
        // given
        var statechanged = false;
        var interceptor1Started = false;
        var interceptor2Started = false;
        var interceptor1Finished = false;
        var interceptor2Finished = false;

        var instances = replaceXHRPrototype();

        // interceptor 1
        inj.onreadystatechange(function(ctx, args) {
            interceptor1Started = true;

            ok(!statechanged);
            ok(!interceptor2Started);
            ok(!interceptor2Finished);

            var result = ctx.proceed(args);

            ok(statechanged);
            ok(interceptor2Started);
            ok(interceptor2Finished);

            interceptor1Finished = true;
            return result;
        });
        // interceptor 2
        inj.onreadystatechange(function(ctx, args) {
            interceptor2Started = true;

            ok(!statechanged);
            ok(interceptor1Started);
            ok(!interceptor1Finished);

            var result = ctx.proceed(args);

            ok(statechanged);
            ok(interceptor1Started);
            ok(!interceptor1Finished);

            interceptor2Finished = true;

            return result;
        });

        // when
        inj.install();
        var xhr = new window.XMLHttpRequest();
        xhr.onreadystatechange = function() {
            statechanged = true;
        }
        ok(instances[0]);
        instances[0].onreadystatechange();

        // then
        ok(statechanged);
        ok(interceptor1Finished);
        ok(interceptor2Finished);
    });

    test("test readystatechange", function() {
        // given
        var readyState = 0;
        var instances = replaceXHRPrototype();

        // when
        inj.install();
        var xhr = new window.XMLHttpRequest();

        var instance = instances[0];
        ok(instance);

        xhr.onreadystatechange = function(request) {
            ok(request.readyState == readyState);
            if (readyState < 4) {
                equal(request.responseText, undefined);
                equal(request.responseXML, undefined);
                equal(request.status, undefined);
                equal(request.statusText, undefined);
            } else {
                equal(request.responseText, "responseText")
                equal(request.responseXML, "responseXML")
                equal(request.status, 200)
                equal(request.statusText, "statusText")
            }
            readyState += 1;
        }

        instance.readyState = 0;
        instance.onreadystatechange();

        instance.readyState = 1;
        instance.onreadystatechange();

        instance.readyState = 2;
        instance.onreadystatechange();

        instance.readyState = 3;
        instance.onreadystatechange();

        instance.readyState = 4;
        instance.responseText = "responseText";
        instance.responseXML = "responseXML";
        instance.status = 200;
        instance.statusText = "statusText";
        instance.onreadystatechange();

        // then
        equal(readyState, 5);
    })
})();<|endoftext|>


<commit_before>// uses node-feedparser to give 'real time' updates for RSS
// watches the feed, firing *after* each interval in a loop
// This stops hammering the feed when the app is restarted
// for updates or whatever
// TODO: conditional GET (if-modified-since and etag)
// Will have to remember old intervals
// using request object
// https://github.com/danmactough/node-feedparser
var fp = require('feedparser')
//var request = require('request')

// update interval is in seconds
// offset allows multiple queries on the same server without hammering
var watch = function (params) {
	if (!params.url) console.error('No URL given')
	if (!params.callback) params.callback = function() {console.error('Callback needed')}

	// silent: yes, I know parseURL is depreciated. See #15
	var parser = new fp ({silent:true})

	// array of known GUIDs
	var known = []

	var interval = {
		// 1 minute
		min: 60,
		// 1 day
		max: 84600,
		// initially set to default value, 5 hours
		current: 18000,
	}

	if (params.interval) interval.current = params.interval

	// offset, to reduce hammering on server restart (updates)
	// and also when checking multiple feeds from the same server
	interval.offset = interval.current*Math.random() + interval.min

	// array of pubdates to unix time, used for calculating update interval
	var dates = []

	// given articles (etc) see if it's new
	// if so, and it to the public callback
	// also ignore the first batch. Real time only.
	var inspect = function(error,meta,articles) {
		if (error) return console.error(params.url,error)

		// oldest first is required to publish in correct order
		articles.reverse()

		// do not publish articles on first run
		if (known.length) {
			articles.forEach( function (article,i) {
				// is this article new? If so, guid is not in known
				if (known.indexOf(article.guid) == -1)
					params.callback(article)

				var date = (new Date(article.pubdate) ).valueOf()
				// convert to seconds
				date /= 1000
				dates.push(date)

				// only keep 20
				if (dates.length > 20)
					dates.shift()
			})
		}

		// recalculate interval (if interval was not explicitly set)
		if (!params.interval)
			calcInterval()

		// GUID GARBAGE COLLECTION, EFFECTIVELY
		// articles which have fallen off the end of the feed stack won't appear again
		// so the old GUIDs may be removed. A simple way is to re-create the known array.
		known = []
		articles.forEach( function (article) {
			known.push(article.guid)
		})

		// fire at next interval (time may change, so timeout is used rather than interval)
		setTimeout(function() { parser.parseUrl(params.url,inspect) },interval.current*1000)
	}

	var calcInterval = function() {
		var periods = []

		dates.sort(function(a,b){return a-b})

		dates.forEach(function (d,i) {
			if (dates[i+1])
				periods.push(dates[i+1] - d)
		})

		// remove potential outliers
		periods.push()
		periods.pop()

		// no meaningful information, leave interval as-is
		if (periods.length < 4) return

		// calculate average
		var avg = 0
		periods.forEach(function(i){
			avg +=i
		})
		avg /= periods.length

		if (avg < interval.min) interval.current = interval.min
		if (avg > interval.max) interval.current = interval.max

		if (avg > interval.min && avg < interval.max )
			interval.current = avg
	}

	// initial run at offset
	setTimeout(function() { parser.parseUrl(params.url,inspect) },interval.offset*1000)
}

var watchMultiple = function (params) {
	if (!params.urls) console.error('No URLs given')
	if (!params.interval) params.interval = 15*60 // 15 minutes
	if (!params.callback) params.callback = function() {console.error('Callback needed')}

	params.urls.forEach(function (url,i) {
		// different offset for each, to avoid hammering
		var offset = i*(params.interval/params.urls.length)
		new watch({
			url:url,
			interval:params.interval,
			callback:params.callback
		})
	})
}

exports.watch = function(params) {return new watch(params)}
exports.watchMultiple = function(params) {return new watchMultiple(params)}

<commit_msg>initial interval 0.2-5 mins<commit_after>// uses node-feedparser to give 'real time' updates for RSS
// watches the feed, firing *after* each interval in a loop
// This stops hammering the feed when the app is restarted
// for updates or whatever
// TODO: conditional GET (if-modified-since and etag)
// Will have to remember old intervals
// using request object
// https://github.com/danmactough/node-feedparser
var fp = require('feedparser')
//var request = require('request')

// update interval is in seconds
// offset allows multiple queries on the same server without hammering
var watch = function (params) {
	if (!params.url) console.error('No URL given')
	if (!params.callback) params.callback = function() {console.error('Callback needed')}

	// silent: yes, I know parseURL is depreciated. See #15
	var parser = new fp ({silent:true})

	// array of known GUIDs
	var known = []

	var interval = {
		// 1 minute
		min: 60,
		// 1 day
		max: 84600,
		// initially set to default value, 5 hours
		current: 18000,
	}

	if (params.interval) interval.current = params.interval

	// offset, to reduce hammering on server restart (updates)
	// and also when checking multiple feeds from the same server
	interval.offset = interval.current*Math.random() + 10

	// array of pubdates to unix time, used for calculating update interval
	var dates = []

	// given articles (etc) see if it's new
	// if so, and it to the public callback
	// also ignore the first batch. Real time only.
	var inspect = function(error,meta,articles) {
		if (error) return console.error(params.url,error)

		// oldest first is required to publish in correct order
		articles.reverse()

		// do not publish articles on first run
		if (known.length) {
			articles.forEach( function (article,i) {
				// is this article new? If so, guid is not in known
				if (known.indexOf(article.guid) == -1)
					params.callback(article)

				var date = (new Date(article.pubdate) ).valueOf()
				// convert to seconds
				date /= 1000
				dates.push(date)

				// only keep 20
				if (dates.length > 20)
					dates.shift()
			})
		}

		// recalculate interval (if interval was not explicitly set)
		if (!params.interval)
			calcInterval()

		// GUID GARBAGE COLLECTION, EFFECTIVELY
		// articles which have fallen off the end of the feed stack won't appear again
		// so the old GUIDs may be removed. A simple way is to re-create the known array.
		known = []
		articles.forEach( function (article) {
			known.push(article.guid)
		})

		// fire at next interval (time may change, so timeout is used rather than interval)
		setTimeout(function() { parser.parseUrl(params.url,inspect) },interval.current*1000)
	}

	var calcInterval = function() {
		var periods = []

		dates.sort(function(a,b){return a-b})

		dates.forEach(function (d,i) {
			if (dates[i+1])
				periods.push(dates[i+1] - d)
		})

		// remove potential outliers
		periods.push()
		periods.pop()

		// no meaningful information, leave interval as-is
		if (periods.length < 4) return

		// calculate average
		var avg = 0
		periods.forEach(function(i){
			avg +=i
		})
		avg /= periods.length

		if (avg < interval.min) interval.current = interval.min
		if (avg > interval.max) interval.current = interval.max

		if (avg > interval.min && avg < interval.max )
			interval.current = avg
	}

	// initial run at offset
	setTimeout(function() { parser.parseUrl(params.url,inspect) },interval.offset*1000)
}

var watchMultiple = function (params) {
	if (!params.urls) console.error('No URLs given')
	if (!params.interval) params.interval = 5*60 // 5 minutes (default, autoscaled to average pubdate interval)
	if (!params.callback) params.callback = function() {console.error('Callback needed')}

	params.urls.forEach(function (url,i) {
		// different offset for each, to avoid hammering
		var offset = i*(params.interval/params.urls.length)
		new watch({
			url:url,
			interval:params.interval,
			callback:params.callback
		})
	})
}

exports.watch = function(params) {return new watch(params)}
exports.watchMultiple = function(params) {return new watchMultiple(params)}

<|endoftext|>


<commit_before>define(['./util'], function(util) {

  "use strict";

  // Namespace: platform
  //
  // Platform specific implementations of common things to do.
  //
  // Method: ajax
  //
  // Set off an HTTP request.
  // Uses CORS, if available on the platform.
  //
  // Parameters:
  //   (given as an *Object*)
  //
  //   url     - URL to send the request to
  //   success - callback function to call when request succeeded
  //   error   - callback function to call when request failed
  //   method  - (optional) HTTP request method to use (default: GET)
  //   headers - (optional) object containing request headers to set
  //   timeout - (optional) milliseconds until request is given up and error
  //             callback is called. If omitted, the request never times out.
  //
  // Example:
  //   (start code)
  //   platform.ajax({
  //     url: "http://en.wikipedia.org/wiki/AJAX",
  //     success: function(responseText, responseHeaders) {
  //       console.log("Here's the page: ", responseText);
  //     },
  //     error: function(errorMessage) {
  //       console.error("Something went wrong: ", errorMessage);
  //     },
  //     timeout: 3000
  //   });
  //   (end code)
  //
  // Platform support:
  //   web browser - YES (if browser <supports CORS at http://caniuse.com/#feat=cors>)
  //   IE - Partially, no support for setting headers.
  //   node - YES, CORS not an issue at all
  //
  //
  // Method: parseXml
  //
  // Parse given XML source.
  //
  // Platform support:
  //   browser - yes, if DOMParser is available
  //   node - yes, if xml2js is available
  //
  //
  // Method: setElementHTML
  //
  // Set the HTML content of an element.
  //
  // Parameters:
  //   element - either an Element or a DOM ID resolving to an element
  //   content - HTML content to set
  //
  // Platform support:
  //   browser - Yes
  //   node - not implemented
  //
  //
  // Method: getElementValue
  //
  //
  // Method: eltOn
  //
  //
  // Method: getLocation
  //
  //
  // Method: setLocation
  //
  //
  // Method: alert
  //

  var logger = util.getLogger('platform');

  // downcase all header keys
  function normalizeHeaders(headers) {
    var h = {};
    for(var key in headers) {
      h[key.toLowerCase()] = headers[key];
    }
    return h;
  }

  function browserParseHeaders(rawHeaders) {
    logger.debug("PARSE HEADERS", typeof(rawHeaders), rawHeaders);
    var headers = {};
    var lines = rawHeaders.split(/\r?\n/);
    var lastKey = null, md, key, value;
    for(var i=0;i<lines.length;i++) {
      if(lines[i].length == 0) {
        // empty line. obviously.
        continue;
      } else if((md = lines[i].match(/^([^:]+):\s*(.+)$/))) {
        // The escaped colon in the following (previously added) comment is
        // necessary, to prevent NaturalDocs from generating a toplevel
        // document called "value line" to the documentation. True story.

        // key\: value line
        key = md[1], value = md[2];
        headers[key] = value;
        lastKey = key;
      } else if((md = lines[i].match(/^\s+(.+)$/))) {
        // continued line (if previous line exceeded 80 bytes
        key = lastKey, value= md[1];
        headers[key] = headers[key] + value;
      } else {
        // nothing we recognize.
        logger.error("Failed to parse header line: " + lines[i]);
      }
    }
    return normalizeHeaders(headers);
  }

  function ajaxBrowser(params) {
    var timedOut = false;
    var timer;
    if(params.timeout) {
      timer = window.setTimeout(function() {
        timedOut = true;
        params.error('timeout');
      }, params.timeout);
    }
    var xhr = new XMLHttpRequest();
    if(!params.method) {
      params.method='GET';
    }
    xhr.open(params.method, params.url, true);
    if(params.headers) {
      for(var header in params.headers) {
        xhr.setRequestHeader(header, params.headers[header]);
      }
    }
    logger.debug('A '+params.url);
    xhr.onreadystatechange = function() {
      if((xhr.readyState==4) && (!timedOut)) {
        logger.debug('B '+params.url);
        if(timer) {
          window.clearTimeout(timer);
        }
        logger.debug('xhr cb '+params.url);
        if(xhr.status==200 || xhr.status==201 || xhr.status==204 || xhr.status==207) {
          window.yxz = xhr;
          params.success(xhr.responseText, browserParseHeaders(xhr.getAllResponseHeaders()));
        } else {
          params.error(xhr.status || 'unknown error');
        }
      }
    }
    logger.debug('xhr '+params.url);
    if(typeof(params.data) === 'string') {
      xhr.send(params.data);
    } else {
      xhr.send();
    }
  }
  function ajaxExplorer(params) {
    //this won't work, because we have no way of sending the Authorization header. It might work for GET to the 'public' category, though.
    var xdr=new XDomainRequest();
    xdr.timeout=params.timeout || 3000;//is this milliseconds? documentation doesn't say
    xdr.open(params.method, params.url);
    xdr.onload=function() {
      if(xdr.status==200 || xdr.status==201 || xdr.status==204) {
        params.success(xhr.responseText);
      } else {
        params.error(xhr.status);
      }
    };
    xdr.onerror = function() {
      err('unknown error');//See http://msdn.microsoft.com/en-us/library/ms536930%28v=vs.85%29.aspx
    };
    xdr.ontimeout = function() {
      err(timeout);
    };
    if(params.data) {
      xdr.send(params.data);
    } else {
      xdr.send();
    }
  }
  function ajaxNode(params) {
    var http=require('http'),
      https=require('https'),
      url=require('url');
    if(!params.method) {
      params.method='GET';
    }
    if(params.data) {
      params.headers['content-length'] = params.data.length;
    } else {
      params.data = null;
    }
    var urlObj = url.parse(params.url);
    var options = {
      method: params.method,
      host: urlObj.hostname,
      path: urlObj.path,
      port: (urlObj.port?port:(urlObj.protocol=='https:'?443:80)),
      headers: params.headers
    };
    var timer, timedOut;

    if(params.timeout) {
      timer = setTimeout(function() {
        params.error('timeout');
        timedOut=true;
      }, params.timeout);
    }

    var lib = (urlObj.protocol=='https:'?https:http);
    var request = lib.request(options, function(response) {
      var str='';
      response.setEncoding('utf8');
      response.on('data', function(chunk) {
        str+=chunk;
      });
      response.on('end', function() {
        if(timer) {
          clearTimeout(timer);
        }
        if(!timedOut) {
          if(response.statusCode==200 || response.statusCode==201 || response.statusCode==204) {
            params.success(str, normalizeHeaders(response.headers));
          } else {
            params.error(response.statusCode);
          }
        }
      });
    });
    request.on('error', function(e) {
      if(timer) {
        clearTimeout(timer);
      }
      params.error(e.message);
    });
    if(params.data) {
      request.end(params.data);
    } else {
      request.end();
    }
  }
  function parseXmlBrowser(str, cb) {
    var tree=(new DOMParser()).parseFromString(str, 'text/xml')
    var nodes=tree.getElementsByTagName('Link');
    var obj={
      Link: []
    };
    for(var i=0; i<nodes.length; i++) {
      var link={};
      if(nodes[i].attributes) {
        for(var j=0; j<nodes[i].attributes.length;j++) {
          link[nodes[i].attributes[j].name]=nodes[i].attributes[j].value;
        }
      }
      var props = nodes[i].getElementsByTagName('Property');
      link.properties = {}
      for(var k=0; k<props.length;k++) {
        link.properties[
          props[k].getAttribute('type')
        ] = props[k].childNodes[0].nodeValue;
      }
      if(link['rel']) {
        obj.Link.push({
          '@': link
        });
      }
    }
    cb(null, obj);
  }
  function parseXmlNode(str, cb) {
    var xml2js=require('xml2js');
    new xml2js.Parser().parseString(str, cb);
  }

  function harvestParamNode() {
  }
  function harvestParamBrowser(param) {
    if(location.hash.length) {
      var pairs = location.hash.substring(1).split('&');
      for(var i=0; i<pairs.length; i++) {
        if(pairs[i].substring(0, (param+'=').length) == param+'=') {
          var ret = decodeURIComponent(pairs[i].substring((param+'=').length));
          delete pairs[i];
          location = '#'+pairs.join('&');
          return ret;
        }
      }
    }
  }
  function setElementHtmlNode(eltName, html) {
  }
  function setElementHtmlBrowser(eltName, html) {
    var elt = eltName;
    if(! (elt instanceof Element)) {
      elt = document.getElementById(eltName);
    }
    elt.innerHTML = html;
  }
  function getElementValueNode(eltName) {
  }
  function getElementValueBrowser(eltName) {
    return document.getElementById(eltName).value;
  }
  function eltOnNode(eltName, eventType, cb) {
  }
  function eltOnBrowser(eltName, eventType, cb) {
    if(eventType == 'click') {
      document.getElementById(eltName).onclick = cb;
    } else if(eventType == 'hover') {
      document.getElementById(eltName).onmouseover = cb;
    } else if(eventType == 'type') {
      document.getElementById(eltName).onkeyup = cb;
    }
  }
  function getLocationBrowser() {
    //TODO: deal with http://user:a#aa@host.com/ although i doubt someone would actually use that even once between now and the end of the internet
    return window.location.href.split('#')[0];
  }
  function getLocationNode() {
  }
  function setLocationBrowser(location) {
    window.location = location;
  }
  function setLocationNode() {
  }
  function alertBrowser(str) {
    alert(str);
  }
  function alertNode(str) {
    console.log(str);
  }
  if(typeof(window) === 'undefined') {
    return {
      ajax: ajaxNode,
      parseXml: parseXmlNode,
      harvestParam: harvestParamNode,
      setElementHTML: setElementHtmlNode,
      getElementValue: getElementValueNode,
      eltOn: eltOnNode,
      getLocation: getLocationNode,
      setLocation: setLocationNode,
      alert: alertNode
    }
  } else {
    if(window.XDomainRequest) {
      return {
        ajax: ajaxExplorer,
        parseXml: parseXmlBrowser,
        harvestParam: harvestParamBrowser,
        setElementHTML: setElementHtmlBrowser,
        getElementValue: getElementValueBrowser,
        eltOn: eltOnBrowser,
        getLocation: getLocationBrowser,
        setLocation: setLocationBrowser,
        alert: alertBrowser
      };
    } else {
      return {
        ajax: ajaxBrowser,
        parseXml: parseXmlBrowser,
        harvestParam: harvestParamBrowser,
        setElementHTML: setElementHtmlBrowser,
        getElementValue: getElementValueBrowser,
        eltOn: eltOnBrowser,
        getLocation: getLocationBrowser,
        setLocation: setLocationBrowser,
        alert: alertBrowser
      };
    }
  }
});
<commit_msg>fixed header handling for firefox, fixes #106<commit_after>define(['./util'], function(util) {

  "use strict";

  // Namespace: platform
  //
  // Platform specific implementations of common things to do.
  //
  // Method: ajax
  //
  // Set off an HTTP request.
  // Uses CORS, if available on the platform.
  //
  // Parameters:
  //   (given as an *Object*)
  //
  //   url     - URL to send the request to
  //   success - callback function to call when request succeeded
  //   error   - callback function to call when request failed
  //   method  - (optional) HTTP request method to use (default: GET)
  //   headers - (optional) object containing request headers to set
  //   timeout - (optional) milliseconds until request is given up and error
  //             callback is called. If omitted, the request never times out.
  //
  // Example:
  //   (start code)
  //   platform.ajax({
  //     url: "http://en.wikipedia.org/wiki/AJAX",
  //     success: function(responseText, responseHeaders) {
  //       console.log("Here's the page: ", responseText);
  //     },
  //     error: function(errorMessage) {
  //       console.error("Something went wrong: ", errorMessage);
  //     },
  //     timeout: 3000
  //   });
  //   (end code)
  //
  // Platform support:
  //   web browser - YES (if browser <supports CORS at http://caniuse.com/#feat=cors>)
  //   IE - Partially, no support for setting headers.
  //   node - YES, CORS not an issue at all
  //
  //
  // Method: parseXml
  //
  // Parse given XML source.
  //
  // Platform support:
  //   browser - yes, if DOMParser is available
  //   node - yes, if xml2js is available
  //
  //
  // Method: setElementHTML
  //
  // Set the HTML content of an element.
  //
  // Parameters:
  //   element - either an Element or a DOM ID resolving to an element
  //   content - HTML content to set
  //
  // Platform support:
  //   browser - Yes
  //   node - not implemented
  //
  //
  // Method: getElementValue
  //
  //
  // Method: eltOn
  //
  //
  // Method: getLocation
  //
  //
  // Method: setLocation
  //
  //
  // Method: alert
  //

  var logger = util.getLogger('platform');

  // downcase all header keys
  function normalizeHeaders(headers) {
    var h = {};
    for(var key in headers) {
      h[key.toLowerCase()] = headers[key];
    }
    return h;
  }

  function browserParseHeaders(rawHeaders) {
    if(! rawHeaders) {
      // firefox bug. workaround in ajaxBrowser.
      return null;
    }
    var headers = {};
    var lines = rawHeaders.split(/\r?\n/);
    var lastKey = null, md, key, value;
    for(var i=0;i<lines.length;i++) {
      if(lines[i].length == 0) {
        // empty line. obviously.
        continue;
      } else if((md = lines[i].match(/^([^:]+):\s*(.+)$/))) {
        // The escaped colon in the following (previously added) comment is
        // necessary, to prevent NaturalDocs from generating a toplevel
        // document called "value line" to the documentation. True story.

        // key\: value line
        key = md[1], value = md[2];
        headers[key] = value;
        lastKey = key;
      } else if((md = lines[i].match(/^\s+(.+)$/))) {
        // continued line (if previous line exceeded 80 bytes
        key = lastKey, value= md[1];
        headers[key] = headers[key] + value;
      } else {
        // nothing we recognize.
        logger.error("Failed to parse header line: " + lines[i]);
      }
    }
    return normalizeHeaders(headers);
  }

  function ajaxBrowser(params) {
    var timedOut = false;
    var timer;
    if(params.timeout) {
      timer = window.setTimeout(function() {
        timedOut = true;
        params.error('timeout');
      }, params.timeout);
    }
    var xhr = new XMLHttpRequest();
    if(!params.method) {
      params.method='GET';
    }
    xhr.open(params.method, params.url, true);
    if(params.headers) {
      for(var header in params.headers) {
        xhr.setRequestHeader(header, params.headers[header]);
      }
    }
    logger.debug('A '+params.url);
    xhr.onreadystatechange = function() {
      if((xhr.readyState==4) && (!timedOut)) {
        logger.debug('B '+params.url);
        if(timer) {
          window.clearTimeout(timer);
        }
        logger.debug('xhr cb '+params.url);
        if(xhr.status==200 || xhr.status==201 || xhr.status==204 || xhr.status==207) {
          var headers = browserParseHeaders(xhr.getAllResponseHeaders());
          if(! headers) {
            // Firefox' getAllResponseHeaders is broken for CORS requests since forever.
            // https://bugzilla.mozilla.org/show_bug.cgi?id=608735
            // Any additional headers that are needed by other code, should be added here.
            headers = {
              'content-type': xhr.getResponseHeader('Content-Type')
            }
          }
          params.success(xhr.responseText, headers);
        } else {
          params.error(xhr.status || 'unknown error');
        }
      }
    }
    logger.debug('xhr '+params.url);
    if(typeof(params.data) === 'string') {
      xhr.send(params.data);
    } else {
      xhr.send();
    }
  }
  function ajaxExplorer(params) {
    //this won't work, because we have no way of sending the Authorization header. It might work for GET to the 'public' category, though.
    var xdr=new XDomainRequest();
    xdr.timeout=params.timeout || 3000;//is this milliseconds? documentation doesn't say
    xdr.open(params.method, params.url);
    xdr.onload=function() {
      if(xdr.status==200 || xdr.status==201 || xdr.status==204) {
        params.success(xhr.responseText);
      } else {
        params.error(xhr.status);
      }
    };
    xdr.onerror = function() {
      err('unknown error');//See http://msdn.microsoft.com/en-us/library/ms536930%28v=vs.85%29.aspx
    };
    xdr.ontimeout = function() {
      err(timeout);
    };
    if(params.data) {
      xdr.send(params.data);
    } else {
      xdr.send();
    }
  }
  function ajaxNode(params) {
    var http=require('http'),
      https=require('https'),
      url=require('url');
    if(!params.method) {
      params.method='GET';
    }
    if(params.data) {
      params.headers['content-length'] = params.data.length;
    } else {
      params.data = null;
    }
    var urlObj = url.parse(params.url);
    var options = {
      method: params.method,
      host: urlObj.hostname,
      path: urlObj.path,
      port: (urlObj.port?port:(urlObj.protocol=='https:'?443:80)),
      headers: params.headers
    };
    var timer, timedOut;

    if(params.timeout) {
      timer = setTimeout(function() {
        params.error('timeout');
        timedOut=true;
      }, params.timeout);
    }

    var lib = (urlObj.protocol=='https:'?https:http);
    var request = lib.request(options, function(response) {
      var str='';
      response.setEncoding('utf8');
      response.on('data', function(chunk) {
        str+=chunk;
      });
      response.on('end', function() {
        if(timer) {
          clearTimeout(timer);
        }
        if(!timedOut) {
          if(response.statusCode==200 || response.statusCode==201 || response.statusCode==204) {
            params.success(str, normalizeHeaders(response.headers));
          } else {
            params.error(response.statusCode);
          }
        }
      });
    });
    request.on('error', function(e) {
      if(timer) {
        clearTimeout(timer);
      }
      params.error(e.message);
    });
    if(params.data) {
      request.end(params.data);
    } else {
      request.end();
    }
  }
  function parseXmlBrowser(str, cb) {
    var tree=(new DOMParser()).parseFromString(str, 'text/xml')
    var nodes=tree.getElementsByTagName('Link');
    var obj={
      Link: []
    };
    for(var i=0; i<nodes.length; i++) {
      var link={};
      if(nodes[i].attributes) {
        for(var j=0; j<nodes[i].attributes.length;j++) {
          link[nodes[i].attributes[j].name]=nodes[i].attributes[j].value;
        }
      }
      var props = nodes[i].getElementsByTagName('Property');
      link.properties = {}
      for(var k=0; k<props.length;k++) {
        link.properties[
          props[k].getAttribute('type')
        ] = props[k].childNodes[0].nodeValue;
      }
      if(link['rel']) {
        obj.Link.push({
          '@': link
        });
      }
    }
    cb(null, obj);
  }
  function parseXmlNode(str, cb) {
    var xml2js=require('xml2js');
    new xml2js.Parser().parseString(str, cb);
  }

  function harvestParamNode() {
  }
  function harvestParamBrowser(param) {
    if(location.hash.length) {
      var pairs = location.hash.substring(1).split('&');
      for(var i=0; i<pairs.length; i++) {
        if(pairs[i].substring(0, (param+'=').length) == param+'=') {
          var ret = decodeURIComponent(pairs[i].substring((param+'=').length));
          delete pairs[i];
          location = '#'+pairs.join('&');
          return ret;
        }
      }
    }
  }
  function setElementHtmlNode(eltName, html) {
  }
  function setElementHtmlBrowser(eltName, html) {
    var elt = eltName;
    if(! (elt instanceof Element)) {
      elt = document.getElementById(eltName);
    }
    elt.innerHTML = html;
  }
  function getElementValueNode(eltName) {
  }
  function getElementValueBrowser(eltName) {
    return document.getElementById(eltName).value;
  }
  function eltOnNode(eltName, eventType, cb) {
  }
  function eltOnBrowser(eltName, eventType, cb) {
    if(eventType == 'click') {
      document.getElementById(eltName).onclick = cb;
    } else if(eventType == 'hover') {
      document.getElementById(eltName).onmouseover = cb;
    } else if(eventType == 'type') {
      document.getElementById(eltName).onkeyup = cb;
    }
  }
  function getLocationBrowser() {
    //TODO: deal with http://user:a#aa@host.com/ although i doubt someone would actually use that even once between now and the end of the internet
    return window.location.href.split('#')[0];
  }
  function getLocationNode() {
  }
  function setLocationBrowser(location) {
    window.location = location;
  }
  function setLocationNode() {
  }
  function alertBrowser(str) {
    alert(str);
  }
  function alertNode(str) {
    console.log(str);
  }
  if(typeof(window) === 'undefined') {
    return {
      ajax: ajaxNode,
      parseXml: parseXmlNode,
      harvestParam: harvestParamNode,
      setElementHTML: setElementHtmlNode,
      getElementValue: getElementValueNode,
      eltOn: eltOnNode,
      getLocation: getLocationNode,
      setLocation: setLocationNode,
      alert: alertNode
    }
  } else {
    if(window.XDomainRequest) {
      return {
        ajax: ajaxExplorer,
        parseXml: parseXmlBrowser,
        harvestParam: harvestParamBrowser,
        setElementHTML: setElementHtmlBrowser,
        getElementValue: getElementValueBrowser,
        eltOn: eltOnBrowser,
        getLocation: getLocationBrowser,
        setLocation: setLocationBrowser,
        alert: alertBrowser
      };
    } else {
      return {
        ajax: ajaxBrowser,
        parseXml: parseXmlBrowser,
        harvestParam: harvestParamBrowser,
        setElementHTML: setElementHtmlBrowser,
        getElementValue: getElementValueBrowser,
        eltOn: eltOnBrowser,
        getLocation: getLocationBrowser,
        setLocation: setLocationBrowser,
        alert: alertBrowser
      };
    }
  }
});
<|endoftext|>