<commit_before>const Sequelize = require("sequelize");
const Op = Sequelize.Op;

const Feed = require("../models/feed");
const Article = require("../models/article");

const autodiscover = require("../util/autodiscover");
const parseFeed = require("../util/parser");
const language = require("../util/language");
const cheerio = require("cheerio");

var striptags = require("striptags");

exports.getCrawl = async (req, res, next) => {
  try {
    const feeds = await Feed.findAll({
      where: {
        active: true
      }
    });

    if (feeds.length > 0) {
      feeds.forEach(async function(feed) {
        //discover rssUrl
        const url = await autodiscover.discover(feed.url);

        //do not process undefined URLs
        if(typeof url !== "undefined") {
          try {
            const feeditem = await parseFeed.process(url);
            if (feeditem) {
              //process all feed posts
              feeditem.posts.forEach(function(post) {
                processArticle(feed, post);
              });

              //reset the feed count
              feed.update({
                errorCount: 0
              });
            }
          } catch (err) {
            console.log(err.stack.split("\n", 1).join(""));
            //update the errorCount
            feed.update({
              errorCount: Sequelize.literal("errorCount + 1")
            });
          }
        }
      });
    }

    return res.status(200).json("Crawling started.");
  } catch (err) {
    console.log(url);
    console.log(err);
  }
};

async function processArticle(feed, post) {
  try {
    const article = await Article.findOne({
      where: {
        [Op.or]: [
          {
            url: post.link
          },
          {
            subject: post.title
          }
        ],
        [Op.and]: {
          feedId: feed.id
        }
      }
    });

    if (!article) {

      //remove any script tags
      const $ = cheerio.load(post.description);
      $('script').remove();

      //add article
      Article.create({
        feedId: feed.id,
        status: "unread",
        star_ind: 0,
        url: post.link,
        image_url: "",
        subject: post.title || 'No title',
        content: $.html(),
        contentStripped: striptags($.html(), ["a", "img", "strong"]),
        language: language.get($.html()),
        //contentSnippet: item.contentSnippet,
        //author: item.author,
        published: post.pubdate
      });
    }
  } catch (err) {
    console.log(err);
  }
}
<commit_msg>dismiss $ undefined errors<commit_after>const Sequelize = require("sequelize");
const Op = Sequelize.Op;

const Feed = require("../models/feed");
const Article = require("../models/article");

const autodiscover = require("../util/autodiscover");
const parseFeed = require("../util/parser");
const language = require("../util/language");
const cheerio = require("cheerio");

var striptags = require("striptags");

exports.getCrawl = async (req, res, next) => {
  try {
    const feeds = await Feed.findAll({
      where: {
        active: true
      }
    });

    if (feeds.length > 0) {
      feeds.forEach(async function(feed) {
        //discover rssUrl
        const url = await autodiscover.discover(feed.url);

        //do not process undefined URLs
        if(typeof url !== "undefined") {
          try {
            const feeditem = await parseFeed.process(url);
            if (feeditem) {
              //process all feed posts
              feeditem.posts.forEach(function(post) {
                processArticle(feed, post);
              });

              //reset the feed count
              feed.update({
                errorCount: 0
              });
            }
          } catch (err) {
            console.log(err.stack.split("\n", 1).join(""));
            //update the errorCount
            feed.update({
              errorCount: Sequelize.literal("errorCount + 1")
            });
          }
        }
      });
    }

    return res.status(200).json("Crawling started.");
  } catch (err) {
    console.log(url);
    console.log(err);
  }
};

async function processArticle(feed, post) {
  try {
    const article = await Article.findOne({
      where: {
        [Op.or]: [
          {
            url: post.link
          },
          {
            subject: post.title
          }
        ],
        [Op.and]: {
          feedId: feed.id
        }
      }
    });

    if (!article) {

      //remove any script tags
      const $ = cheerio.load(String(post.description));

      //dismiss undefined errors
      if (typeof $ !== 'undefined') {
        $('script').remove();

        //add article
        Article.create({
          feedId: feed.id,
          status: "unread",
          star_ind: 0,
          url: post.link,
          image_url: "",
          subject: post.title || 'No title',
          content: $.html(),
          contentStripped: striptags($.html(), ["a", "img", "strong"]),
          language: language.get($.html()),
          //contentSnippet: item.contentSnippet,
          //author: item.author,
          published: post.pubdate
        });
      }
    }
  } catch (err) {
    console.log(err);
  }
}
<|endoftext|>


<commit_before>import path from 'path';
import { expect } from 'chai';

import MatmanConfig from '../../src/MatmanConfig';

describe('check MatmanConfig.js', () => {
    describe('check construction', () => {
        it('if rootPath is undefined return false', () => {
            let checkResult = new MatmanConfig().check();

            expect(checkResult.result).to.be.false;
            expect(checkResult.msg).to.match(/Unknown rootPath=.*/);
        });

        it('if rootPath is not exist return false', () => {
            let checkResult = new MatmanConfig('not/exist/rootPath').check();

            expect(checkResult.result).to.be.false;
            expect(checkResult.msg).to.match(/Unknown rootPath=.*not\/exist\/rootPath/);
        });

        it('if params.testerPath is not exist return false', () => {
            let checkResult = new MatmanConfig(__dirname, { testerPath: 'not/exist/testerPath' }).check();

            expect(checkResult.result).to.be.false;
            expect(checkResult.msg).to.match(/Unknown testerPath=.*not\/exist\/testerPath/);
        });

        it('check demo1: default value', () => {
            let rootPath = path.join(__dirname, '../data/fixtures/demo1');

            let matmanConfig = new MatmanConfig(rootPath);

            expect(matmanConfig.check().result).to.be.true;
            expect(matmanConfig.rootPath).to.equal(rootPath);
            expect(matmanConfig.testerPath).to.equal(path.join(rootPath, './src/testers'));
            expect(matmanConfig.crawlerBuildPath).to.equal(path.join(rootPath, './build/crawler-script'));
            expect(matmanConfig.crawlerMatch).to.eql(/[\/|\\]crawlers[\/|\\].*\.js$/);
            expect(matmanConfig.crawlerInjectJQuery).to.be.true;
            expect(matmanConfig.isDevBuild).to.be.false;
            expect(matmanConfig.screenshotPath).to.equal(path.join(rootPath, './build/screenshot'));
            expect(matmanConfig.coveragePath).to.equal(path.join(rootPath, './build/coverage_output'));

            expect(matmanConfig).to.have.all.keys('rootPath', 'testerPath', 'crawlerBuildPath', 'crawlerMatch', 'crawlerInjectJQuery', 'isDevBuild', 'screenshotPath', 'coveragePath');
        });

        it('check demo2: custom value', () => {
            let rootPath = path.join(__dirname, '../data/fixtures/demo2');

            let matmanConfig = new MatmanConfig(rootPath, {
                testerPath: './src-testers',
                crawlerBuildPath: path.join(rootPath, './build/my-crawler-script'),
                crawlerMatch: /[\/|\\]my-crawlers[\/|\\].*\.js$/,
                crawlerInjectJQuery: false,
                screenshotPath: './build/my-screenshot',
                coveragePath: './build/my-coverage_output',
                isDevBuild: true
            });

            expect(matmanConfig.check().result).to.be.true;
            expect(matmanConfig.rootPath).to.equal(rootPath);
            expect(matmanConfig.testerPath).to.equal(path.join(rootPath, './src-testers'));
            expect(matmanConfig.crawlerBuildPath).to.equal(path.join(rootPath, './build/my-crawler-script_dev'));
            expect(matmanConfig.crawlerMatch).to.eql(/[\/|\\]my-crawlers[\/|\\].*\.js$/);
            expect(matmanConfig.crawlerInjectJQuery).to.be.false;
            expect(matmanConfig.isDevBuild).to.be.true;
            expect(matmanConfig.screenshotPath).to.equal(path.join(rootPath, './build/my-screenshot'));
            expect(matmanConfig.coveragePath).to.equal(path.join(rootPath, './build/my-coverage_output'));

            expect(matmanConfig).to.have.all.keys('rootPath', 'testerPath', 'crawlerBuildPath', 'crawlerMatch', 'crawlerInjectJQuery', 'isDevBuild', 'screenshotPath', 'coveragePath');
        });
    });

});

<commit_msg>feat: 补充 getAbsolutePath 自动化测试<commit_after>import path from 'path';
import { expect } from 'chai';

import MatmanConfig, { getAbsolutePath } from '../../src/MatmanConfig';

describe('check MatmanConfig.js', () => {
    describe('check constructor(rootPath, opts = {})', () => {
        it('if rootPath is undefined return false', () => {
            let checkResult = new MatmanConfig().check();

            expect(checkResult.result).to.be.false;
            expect(checkResult.msg).to.match(/Unknown rootPath=.*/);
        });

        it('if rootPath is not exist return false', () => {
            let checkResult = new MatmanConfig('not/exist/rootPath').check();

            expect(checkResult.result).to.be.false;
            expect(checkResult.msg).to.match(/Unknown rootPath=.*not\/exist\/rootPath/);
        });

        it('if params.testerPath is not exist return false', () => {
            let checkResult = new MatmanConfig(__dirname, { testerPath: 'not/exist/testerPath' }).check();

            expect(checkResult.result).to.be.false;
            expect(checkResult.msg).to.match(/Unknown testerPath=.*not\/exist\/testerPath/);
        });

        it('check demo1: default value', () => {
            let rootPath = path.join(__dirname, '../data/fixtures/demo1');

            let matmanConfig = new MatmanConfig(rootPath);

            expect(matmanConfig.check().result).to.be.true;
            expect(matmanConfig.rootPath).to.equal(rootPath);
            expect(matmanConfig.testerPath).to.equal(path.join(rootPath, './src/testers'));
            expect(matmanConfig.crawlerBuildPath).to.equal(path.join(rootPath, './build/crawler-script'));
            expect(matmanConfig.crawlerMatch).to.eql(/[\/|\\]crawlers[\/|\\].*\.js$/);
            expect(matmanConfig.crawlerInjectJQuery).to.be.true;
            expect(matmanConfig.isDevBuild).to.be.false;
            expect(matmanConfig.screenshotPath).to.equal(path.join(rootPath, './build/screenshot'));
            expect(matmanConfig.coveragePath).to.equal(path.join(rootPath, './build/coverage_output'));

            expect(matmanConfig).to.have.all.keys('rootPath', 'testerPath', 'crawlerBuildPath', 'crawlerMatch', 'crawlerInjectJQuery', 'isDevBuild', 'screenshotPath', 'coveragePath');
        });

        it('check demo2: custom value', () => {
            let rootPath = path.join(__dirname, '../data/fixtures/demo2');

            let matmanConfig = new MatmanConfig(rootPath, {
                testerPath: './src-testers',
                crawlerBuildPath: path.join(rootPath, './build/my-crawler-script'),
                crawlerMatch: /[\/|\\]my-crawlers[\/|\\].*\.js$/,
                crawlerInjectJQuery: false,
                screenshotPath: './build/my-screenshot',
                coveragePath: './build/my-coverage_output',
                isDevBuild: true
            });

            expect(matmanConfig.check().result).to.be.true;
            expect(matmanConfig.rootPath).to.equal(rootPath);
            expect(matmanConfig.testerPath).to.equal(path.join(rootPath, './src-testers'));
            expect(matmanConfig.crawlerBuildPath).to.equal(path.join(rootPath, './build/my-crawler-script_dev'));
            expect(matmanConfig.crawlerMatch).to.eql(/[\/|\\]my-crawlers[\/|\\].*\.js$/);
            expect(matmanConfig.crawlerInjectJQuery).to.be.false;
            expect(matmanConfig.isDevBuild).to.be.true;
            expect(matmanConfig.screenshotPath).to.equal(path.join(rootPath, './build/my-screenshot'));
            expect(matmanConfig.coveragePath).to.equal(path.join(rootPath, './build/my-coverage_output'));

            expect(matmanConfig).to.have.all.keys('rootPath', 'testerPath', 'crawlerBuildPath', 'crawlerMatch', 'crawlerInjectJQuery', 'isDevBuild', 'screenshotPath', 'coveragePath');
        });
    });

    describe('check getAbsolutePath(targetPath, basePath)', () => {
        it('targetPath is absolute and basePath is undefined', () => {
            expect(getAbsolutePath('/home/i/am/absolute/path')).to.equal('/home/i/am/absolute/path');
        });

        it('targetPath is absolute and basePath is relative', () => {
            expect(getAbsolutePath('/home/i/am/absolute/path', './relative/path')).to.equal('/home/i/am/absolute/path');
        });

        it('targetPath is absolute and basePath is absolute', () => {
            expect(getAbsolutePath('/home/i/am/absolute/path', '/data/another/absolute/path')).to.equal('/home/i/am/absolute/path');
        });

        it('targetPath is relative and basePath is undefined', () => {
            expect(getAbsolutePath('./i/am/relative/path')).to.equal(path.resolve('./i/am/relative/path'));
        });

        it('targetPath is relative and basePath is relative', () => {
            expect(getAbsolutePath('./i/am/relative/path', './relative2/path')).to.equal(path.resolve('./relative2/path', './i/am/relative/path'));
        });

        it('targetPath is relative and basePath is absolute', () => {
            expect(getAbsolutePath('./i/am/relative/path', '/data/another/absolute/path')).to.equal('/data/another/absolute/path/i/am/relative/path');
        });
    });
});

<|endoftext|>


<commit_before>/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
/* eslint-disable max-len */
'use strict';

const escapeHTML = require('./escapeHTML');

const reactTestInstance = Symbol.for('react.test.json');

function printChildren(children, print, indent, colors, opts) {
  return children.map(child => printInstance(child, print, indent, colors, opts)).join(opts.edgeSpacing);
}

function printProps(props, print, indent, colors, opts) {
  return Object.keys(props).sort().map(name => {
    const prop = props[name];
    let printed = print(prop);

    if (typeof prop !== 'string') {
      if (printed.indexOf('\n') !== -1) {
        printed = '{' + opts.edgeSpacing + indent(indent(printed) + opts.edgeSpacing + '}');
      } else {
        printed = '{' + printed + '}';
      }
    }

    return opts.spacing + indent(colors.prop.open + name + colors.prop.close + '=') + colors.value.open + printed + colors.value.close;
  }).join('');
}

function printInstance(instance, print, indent, colors, opts) {
  if (typeof instance == 'number') {
    return print(instance);
  } else if (typeof instance === 'string') {
    return colors.content.open + escapeHTML(instance) + colors.content.close;
  }

  let result = colors.tag.open + '<' + instance.type + colors.tag.close;

  if (instance.props) {
    result += printProps(instance.props, print, indent, colors, opts);
  }

  const closeInNewLine = !!Object.keys(instance.props).length && !opts.min;

  if (instance.children) {
    const children = printChildren(instance.children, print, indent, colors, opts);
    result += colors.tag.open + (closeInNewLine ? '\n' : '') + '>' + colors.tag.close + opts.edgeSpacing + indent(children) + opts.edgeSpacing + colors.tag.open + '</' + instance.type + '>' + colors.tag.close;
  } else {
    result += colors.tag.open + (closeInNewLine ? '\n' : ' ') + '/>' + colors.tag.close;
  }

  return result;
}

module.exports = {
  print(val, print, indent, opts, colors) {
    return printInstance(val, print, indent, colors, opts);
  },
  test(object) {
    return object && object.$$typeof === reactTestInstance;
  },
};
<commit_msg>Keep supporting undefined props in ReactTestComponent plugin (#2943)<commit_after>/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
/* eslint-disable max-len */
'use strict';

const escapeHTML = require('./escapeHTML');

const reactTestInstance = Symbol.for('react.test.json');

function printChildren(children, print, indent, colors, opts) {
  return children.map(child => printInstance(child, print, indent, colors, opts)).join(opts.edgeSpacing);
}

function printProps(props, print, indent, colors, opts) {
  return Object.keys(props).sort().map(name => {
    const prop = props[name];
    let printed = print(prop);

    if (typeof prop !== 'string') {
      if (printed.indexOf('\n') !== -1) {
        printed = '{' + opts.edgeSpacing + indent(indent(printed) + opts.edgeSpacing + '}');
      } else {
        printed = '{' + printed + '}';
      }
    }

    return opts.spacing + indent(colors.prop.open + name + colors.prop.close + '=') + colors.value.open + printed + colors.value.close;
  }).join('');
}

function printInstance(instance, print, indent, colors, opts) {
  if (typeof instance == 'number') {
    return print(instance);
  } else if (typeof instance === 'string') {
    return colors.content.open + escapeHTML(instance) + colors.content.close;
  }

  let result = colors.tag.open + '<' + instance.type + colors.tag.close;
  let closeInNewLine = false;

  if (instance.props) {
    result += printProps(instance.props, print, indent, colors, opts);
    closeInNewLine = !!Object.keys(instance.props).length && !opts.min;
  }

  if (instance.children) {
    const children = printChildren(instance.children, print, indent, colors, opts);
    result += colors.tag.open + (closeInNewLine ? '\n' : '') + '>' + colors.tag.close + opts.edgeSpacing + indent(children) + opts.edgeSpacing + colors.tag.open + '</' + instance.type + '>' + colors.tag.close;
  } else {
    result += colors.tag.open + (closeInNewLine ? '\n' : ' ') + '/>' + colors.tag.close;
  }

  return result;
}

module.exports = {
  print(val, print, indent, opts, colors) {
    return printInstance(val, print, indent, colors, opts);
  },
  test(object) {
    return object && object.$$typeof === reactTestInstance;
  },
};
<|endoftext|>


<commit_before>import React from "react";
import prefixer from "../utils/prefixer";
import cx from 'classnames';

//TODO Change this to a self contained component from the current version that requires too much logic in the parent component
class ProgressBar extends React.PureComponent {
  calculateRatio(value) {
    if (value < this.props.min) {
      return 0;
    }
    if (value > this.props.max) {
      return 1;
    }
    return (value - this.props.min) / (this.props.max - this.props.min);
  }

  circularStyle() {
    if (this.props.mode !== "indeterminate") {
      return {
        "strokeDasharray":
          `${2 * Math.PI * 25 * this.calculateRatio(this.props.value)}, 400`

      };
    }
  }

  linearStyle() {
    if (this.props.mode !== "indeterminate") {
      return {
        "buffer": prefixer({
          "transform": `scaleX(${this.calculateRatio(this.props.buffer)})`,
          "transitionDuration": this.props.transitionDuration
        }),
        "value": prefixer({
          "transform": `scaleX(${this.calculateRatio(this.props.value)})`,
          "transitionDuration": this.props.transitionDuration
        })
      };
    } else {
      return {};
    }
  }

  renderCircular() {
    return (
      <svg styleName={cx("circle")}>
        <circle
          styleName={cx("path")}
          style={this.circularStyle()}
          cx="30"
          cy="30"
          r="25"
        />
      </svg>
    );
  }

  renderLinear() {
    const { buffer, value } = this.linearStyle();
    return (

    );
  }
  renderRange() {
    let rangeStyle = prefixer({
      "transform":
        `translateX(${this.calculateRatio(this.props.value.from) * 100}%)
                   scaleX(${this.calculateRatio(
          this.props.value.to - this.props.value.from
        )})`

    });
    return (

    );
  }

  renderInner() {
    if (this.props.type === "circular") {
      return this.renderCircular();
    }
    if (isNaN(this.props.value)) {
      return this.renderRange();
    }
    return this.renderLinear();
  }

  render() {
    const { type, mode, value, min, max, className} = this.props;
    const classes = [
      type,
      mode,
      className
    ];

    return (

        {this.renderInner()}

    );
  }
}

ProgressBar.propTypes = {
  /**
   * The value of a second progress bar
   * @examples ''
   */
  "buffer": React.PropTypes.number,
  /**
   * Additional classname provided to component for further styling
   * @examples ''
   */
  "className": React.PropTypes.string,
  /**
   * Length of time in seconds for the transition (can use decimals)
   * @examples '35'
   */
  "transitionDuration": React.PropTypes.string,
  /**
   * The max value of the progress bar
   * @examples ''
   */
  "max": React.PropTypes.number,
  /**
   * The min value of the progress bar
   * @examples ''
   */
  "min": React.PropTypes.number,
  /**
   * Defines the type of Progress bar: 'determinate', 'indeterminate'
   * @examples 'circular'
   */
  "mode": React.PropTypes.string,
  /**
   * If true, progressbar will change colors during transition
   * @examples ''
   */
  "multicolor": React.PropTypes.bool,
  /**
   * Type of progress bar; 'circular' or 'linear'
   * @examples ''
   */
  "type": React.PropTypes.oneOf(["linear", "circular"]),
  /**
   * The default value(s) of the progress bar.  Can be a number, or an object containing keys of "from" and "to"
   * @examples '{"from": 10, "to": 80" }'
   */
  "value": React.PropTypes.oneOfType([
    React.PropTypes.number,
    React.PropTypes.shape({
      "from": React.PropTypes.number,
      "to": React.PropTypes.number
    })
  ])
};

ProgressBar.defaultProps = {
  "buffer": 0,
  "className": "",
  "max": 100,
  "min": 0,
  "transitionDuration": ".35s",
  "mode": "indeterminate",
  "multicolor": false,
  "type": "linear",
  "value": 0
};

ProgressBar.styleguide = {
  "category": "Form Components",
  "index": "3.7",
  "wrappedExample": true,
  "example":
    `
// Internal Methods {
class ProgressBarExample extends React.Component {
  state = {
    progress: 0,
    buffer: 10
  };
  componentWillMount () {
    this.simulateProgress();
  }
  simulateProgress () {
    setTimeout(() => {
      if (this.state.progress < 100) {
        this.increaseProgress();
        if (this.state.progress > this.state.buffer) this.increaseBuffer();
      } else {
        this.setState(this.state);
      }
      this.simulateProgress();
    }, (Math.random() * 1 + 1) * 1000);
  }
  increaseProgress () {
    this.setState({
      progress: Math.random() * 30 + this.state.progress
    });
  }
  increaseBuffer () {
    this.setState({
      buffer: Math.random() * (100 - this.state.progress) + this.state.progress
    });
  }
// }
  render () {
    return (

Progress bars

Determinate

        <ProgressBar mode="determinate" value={this.state.progress} buffer={this.state.buffer}/>

Indeterminate...

        <ProgressBar mode="indeterminate"/>

Circular Indeterminate

        <ProgressBar type="circular" mode="indeterminate"/>

Circular Determinate

        <ProgressBar type="circular" mode="determinate" value={this.state.progress}/>

Range

        <ProgressBar mode='determinate' value={{from: 10, to: 80}}/>

    );
  }
// Mount Component {
}
ReactDOM.render(<ProgressBarExample/>, mountNode);
// }
`

};

export default ProgressBar;<commit_msg>Remove styleguide.<commit_after>import React from "react";
import prefixer from "../utils/prefixer";
import cx from 'classnames';

//TODO Change this to a self contained component from the current version that requires too much logic in the parent component
class ProgressBar extends React.PureComponent {
  calculateRatio(value) {
    if (value < this.props.min) {
      return 0;
    }
    if (value > this.props.max) {
      return 1;
    }
    return (value - this.props.min) / (this.props.max - this.props.min);
  }

  circularStyle() {
    if (this.props.mode !== "indeterminate") {
      return {
        "strokeDasharray":
          `${2 * Math.PI * 25 * this.calculateRatio(this.props.value)}, 400`

      };
    }
  }

  linearStyle() {
    if (this.props.mode !== "indeterminate") {
      return {
        "buffer": prefixer({
          "transform": `scaleX(${this.calculateRatio(this.props.buffer)})`,
          "transitionDuration": this.props.transitionDuration
        }),
        "value": prefixer({
          "transform": `scaleX(${this.calculateRatio(this.props.value)})`,
          "transitionDuration": this.props.transitionDuration
        })
      };
    } else {
      return {};
    }
  }

  renderCircular() {
    return (
      <svg styleName={cx("circle")}>
        <circle
          styleName={cx("path")}
          style={this.circularStyle()}
          cx="30"
          cy="30"
          r="25"
        />
      </svg>
    );
  }

  renderLinear() {
    const { buffer, value } = this.linearStyle();
    return (

    );
  }
  renderRange() {
    let rangeStyle = prefixer({
      "transform":
        `translateX(${this.calculateRatio(this.props.value.from) * 100}%)
                   scaleX(${this.calculateRatio(
          this.props.value.to - this.props.value.from
        )})`

    });
    return (

    );
  }

  renderInner() {
    if (this.props.type === "circular") {
      return this.renderCircular();
    }
    if (isNaN(this.props.value)) {
      return this.renderRange();
    }
    return this.renderLinear();
  }

  render() {
    const { type, mode, value, min, max, className} = this.props;
    const classes = [
      type,
      mode,
      className
    ];

    return (

        {this.renderInner()}

    );
  }
}

ProgressBar.propTypes = {
  /**
   * The value of a second progress bar
   * @examples ''
   */
  "buffer": React.PropTypes.number,
  /**
   * Additional classname provided to component for further styling
   * @examples ''
   */
  "className": React.PropTypes.string,
  /**
   * Length of time in seconds for the transition (can use decimals)
   * @examples '35'
   */
  "transitionDuration": React.PropTypes.string,
  /**
   * The max value of the progress bar
   * @examples ''
   */
  "max": React.PropTypes.number,
  /**
   * The min value of the progress bar
   * @examples ''
   */
  "min": React.PropTypes.number,
  /**
   * Defines the type of Progress bar: 'determinate', 'indeterminate'
   * @examples 'circular'
   */
  "mode": React.PropTypes.string,
  /**
   * If true, progressbar will change colors during transition
   * @examples ''
   */
  "multicolor": React.PropTypes.bool,
  /**
   * Type of progress bar; 'circular' or 'linear'
   * @examples ''
   */
  "type": React.PropTypes.oneOf(["linear", "circular"]),
  /**
   * The default value(s) of the progress bar.  Can be a number, or an object containing keys of "from" and "to"
   * @examples '{"from": 10, "to": 80" }'
   */
  "value": React.PropTypes.oneOfType([
    React.PropTypes.number,
    React.PropTypes.shape({
      "from": React.PropTypes.number,
      "to": React.PropTypes.number
    })
  ])
};

ProgressBar.defaultProps = {
  "buffer": 0,
  "className": "",
  "max": 100,
  "min": 0,
  "transitionDuration": ".35s",
  "mode": "indeterminate",
  "multicolor": false,
  "type": "linear",
  "value": 0
};

export default ProgressBar;<|endoftext|>


<commit_before>import React from "react";
import PropTypes from "prop-types";
import prefixer from "../utils/prefixer";
import cx from "classnames";

//TODO Change this to a self contained component from the current version that requires too much logic in the parent component
class ProgressBar extends React.PureComponent {
  calculateRatio(value) {
    if (value < this.props.min) {
      return 0;
    }
    if (value > this.props.max) {
      return 1;
    }
    return (value - this.props.min) / (this.props.max - this.props.min);
  }

  circularStyle() {
    if (this.props.mode !== "indeterminate") {
      return {
        "strokeDasharray": `${2 *
          Math.PI *
          25 *
          this.calculateRatio(this.props.value)}, 400`
      };
    }

    return null;
  }

  linearStyle() {
    if (this.props.mode !== "indeterminate") {
      return {
        "buffer": prefixer({
          "transform": `scaleX(${this.calculateRatio(this.props.buffer)})`,
          "transitionDuration": this.props.transitionDuration
        }),
        "value": prefixer({
          "transform": `scaleX(${this.calculateRatio(this.props.value)})`,
          "transitionDuration": this.props.transitionDuration
        })
      };
    } else {
      return {};
    }
  }

  renderCircular() {
    return (
      <svg styleName={"circle"}>
        <circle
          styleName={"path"}
          style={this.circularStyle()}
          cx="30"
          cy="30"
          r="25"
        />
      </svg>
    );
  }

  renderLinear() {
    const { buffer, value } = this.linearStyle();
    return (

    );
  }
  renderRange() {
    let rangeStyle = prefixer({
      "transform": `translateX(${this.calculateRatio(this.props.value.from) *
        100}%)
                   scaleX(${this.calculateRatio(
                     this.props.value.to - this.props.value.from
                   )})`
    });
    return ;
  }

  renderInner() {
    if (this.props.type === "circular") {
      return this.renderCircular();
    }
    if (isNaN(this.props.value)) {
      return this.renderRange();
    }
    return this.renderLinear();
  }

  render() {
    const { type, mode, value, min, max, className, style } = this.props;
    const classes = [type, mode, className];

    return (

        {this.renderInner()}

    );
  }
}

ProgressBar.propTypes = {
  /**
   * The value of a second progress bar
   * @examples ''
   */
  "buffer": PropTypes.number,
  /** An Object, array, or string of CSS classes to apply to ProgressBar.*/
  "className": PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.object,
    PropTypes.array
  ]),
  /**
   * Length of time in seconds for the transition (can use decimals)
   * @examples '35'
   */
  "transitionDuration": PropTypes.string,
  /**
   * The max value of the progress bar
   * @examples ''
   */
  "max": PropTypes.number,
  /**
   * The min value of the progress bar
   * @examples ''
   */
  "min": PropTypes.number,
  /**
   * Defines the type of Progress bar: 'determinate', 'indeterminate'
   * @examples 'circular'
   */
  "mode": PropTypes.string,
  /**
   * If true, progressbar will change colors during transition
   * @examples ''
   */
  "multicolor": PropTypes.bool,
  /**
   * Type of progress bar; 'circular' or 'linear'
   * @examples ''
   */
  "type": PropTypes.oneOf(["linear", "circular"]),
  /**
   * The default value(s) of the progress bar.  Can be a number, or an object containing keys of "from" and "to"
   * @examples '{"from": 10, "to": 80" }'
   */
  "value": PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.shape({
      "from": PropTypes.number,
      "to": PropTypes.number
    })
  ]),

  /**
   * Pass inline styling here.
   */
  "style": PropTypes.object
};

ProgressBar.defaultProps = {
  "buffer": 0,
  "className": "",
  "max": 100,
  "min": 0,
  "transitionDuration": ".35s",
  "mode": "indeterminate",
  "multicolor": false,
  "type": "linear",
  "value": 0
};

export default ProgressBar;
<commit_msg>fix story 25<commit_after>import React from "react";
import PropTypes from "prop-types";
import prefixer from "../utils/prefixer";
import cx from "classnames";

//TODO Change this to a self contained component from the current version that requires too much logic in the parent component
class ProgressBar extends React.PureComponent {
  calculateRatio(value) {
    if (value < this.props.min) {
      return 0;
    }
    if (value > this.props.max) {
      return 1;
    }
    return (value - this.props.min) / (this.props.max - this.props.min);
  }

  circularStyle() {
    if (this.props.mode !== "indeterminate") {
      return {
        "strokeDasharray": `${2 *
          Math.PI *
          25 *
          this.calculateRatio(this.props.value)}, 400`
      };
    } else {
      return {}
    }
  }

  linearStyle() {
    if (this.props.mode !== "indeterminate") {
      return {
        "buffer": prefixer({
          "transform": `scaleX(${this.calculateRatio(this.props.buffer)})`,
          "transitionDuration": this.props.transitionDuration
        }),
        "value": prefixer({
          "transform": `scaleX(${this.calculateRatio(this.props.value)})`,
          "transitionDuration": this.props.transitionDuration,
        })
      };
    } else {
      return {};
    }
  }

  renderCircular() {
    const strokeDasharray = this.circularStyle();

    let color = this.props.color;

    const styles = {
      "stroke": color
    };

    const style = Object.assign(strokeDasharray, styles);

    return (
      <svg styleName={"circle"}>
        <circle
          styleName={"path"}
          style={style}
          cx="30"
          cy="30"
          r="25"
        />
      </svg>
    );
  }

  renderLinear() {
    const { buffer, value } = this.linearStyle();
    let color = this.props.color;

    const styles = prefixer({
      "backgroundColor": color
    }, value);

    return (

    );
  }

  renderRange() {
    let rangeStyle = prefixer({
      "transform": `translateX(${this.calculateRatio(this.props.value.from) *
        100}%)
                   scaleX(${this.calculateRatio(
                     this.props.value.to - this.props.value.from
                   )})`
    });
    return ;
  }

  renderInner() {
    if (this.props.type === "circular") {
      return this.renderCircular();
    }
    if (isNaN(this.props.value)) {
      return this.renderRange();
    }
    return this.renderLinear();
  }

  render() {
    const { type, mode, value, min, max, className, style } = this.props;
    const classes = [type, mode, className];

    return (

        {this.renderInner()}

    );
  }
}

ProgressBar.propTypes = {
  /**
   * The value of a second progress bar
   * @examples ''
   */
  "buffer": PropTypes.number,
  /** An Object, array, or string of CSS classes to apply to ProgressBar.*/
  "className": PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.object,
    PropTypes.array
  ]),
  /**
   * Sets color for the upfront color.
   * @examples '<ProgressBar color="red"/>'
   */
  "color": PropTypes.string,
  /**
   * Length of time in seconds for the transition (can use decimals)
   * @examples '35'
   */
  "transitionDuration": PropTypes.string,
  /**
   * The max value of the progress bar
   * @examples ''
   */
  "max": PropTypes.number,
  /**
   * The min value of the progress bar
   * @examples ''
   */
  "min": PropTypes.number,
  /**
   * Defines the type of Progress bar: 'determinate', 'indeterminate'
   * @examples 'circular'
   */
  "mode": PropTypes.string,
  /**
   * If true, progressbar will change colors during transition
   * @examples ''
   */
  "multicolor": PropTypes.bool,
  /**
   * Type of progress bar; 'circular' or 'linear'
   * @examples ''
   */
  "type": PropTypes.oneOf(["linear", "circular"]),
  /**
   * The default value(s) of the progress bar.  Can be a number, or an object containing keys of "from" and "to"
   * @examples '{"from": 10, "to": 80" }'
   */
  "value": PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.shape({
      "from": PropTypes.number,
      "to": PropTypes.number
    })
  ]),

  /**
   * Pass inline styling here.
   */
  "style": PropTypes.object
};

ProgressBar.defaultProps = {
  "buffer": 0,
  "className": "",
  "max": 100,
  "min": 0,
  "transitionDuration": ".35s",
  "mode": "indeterminate",
  "multicolor": false,
  "type": "linear",
  "value": 0
};

export default ProgressBar;
<|endoftext|>


<commit_before>/**
 * Copyright IBM Corp. 2016, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

import PropTypes from 'prop-types';
import React, { Component } from 'react';
import classNames from 'classnames';
import { settings } from 'carbon-components';
import {
  WarningFilled16,
  CaretDownGlyph,
  CaretUpGlyph,
} from '@carbon/icons-react';
import mergeRefs from '../../tools/mergeRefs';
import requiredIfValueExists from '../../prop-types/requiredIfValueExists';
import { useControlledStateWithValue } from '../../internal/FeatureFlags';

const { prefix } = settings;

export const translationIds = {
  'increment.number': 'increment.number',
  'decrement.number': 'decrement.number',
};

const defaultTranslations = {
  [translationIds['increment.number']]: 'Increment number',
  [translationIds['decrement.number']]: 'Decrement number',
};

const capMin = (min, value) =>
  isNaN(min) || (!min && min !== 0) || isNaN(value) || (!value && value !== 0)
    ? value
    : Math.max(min, value);
const capMax = (max, value) =>
  isNaN(max) || (!max && max !== 0) || isNaN(value) || (!value && value !== 0)
    ? value
    : Math.min(max, value);

class NumberInput extends Component {
  static propTypes = {
    /**
     * Specify an optional className to be applied to the wrapper node
     */
    className: PropTypes.string,
    /**
     * Specify if the control should be disabled, or not
     */
    disabled: PropTypes.bool,
    /**
     * Specify whether you want the underlying label to be visually hidden
     */
    hideLabel: PropTypes.bool,
    /**
     * Provide a description for up/down icons that can be read by screen readers
     */
    iconDescription: PropTypes.string.isRequired,
    /**
     * Specify a custom `id` for the input
     */
    id: PropTypes.string.isRequired,
    /**
     * Generic `label` that will be used as the textual representation of what
     * this field is for
     */
    label: PropTypes.node,
    /**
     * The maximum value.
     */
    max: PropTypes.number,
    /**
     * The minimum value.
     */
    min: PropTypes.number,
    /**
     * The new value is available in 'imaginaryTarget.value'
     * i.e. to get the value: evt.imaginaryTarget.value
     *
     * * _With_ `useControlledStateWithValue` feature flag, the signature of the event handler will be altered to provide additional context in the second parameter: `onChange(event, { value, direction })` where:
     *   * `event` is the (React) raw event
     *   * `value` is the new value
     *   * `direction` tells you the button you hit is up button or down button
     * * _Without_ this feature flag the event handler has `onChange(event, direction)` signature.
     */
    onChange: !useControlledStateWithValue
      ? PropTypes.func
      : requiredIfValueExists(PropTypes.func),
    /**
     * Provide an optional function to be called when the up/down button is clicked
     */
    onClick: PropTypes.func,
    /**
     * Specify how much the valus should increase/decrease upon clicking on up/down button
     */
    step: PropTypes.number,
    /**
     * Optional starting value for uncontrolled state
     */
    defaultValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    /**
     * Specify the value of the input
     */
    value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    /**
     * Specify if the component should be read-only
     */
    readOnly: PropTypes.bool,
    /**
     * Specify if the currently value is invalid.
     */
    invalid: PropTypes.bool,
    /**
     * Message which is displayed if the value is invalid.
     */
    invalidText: PropTypes.string,
    /**
     * Provide text that is used alongside the control label for additional help
     */
    helperText: PropTypes.node,
    /**
     * Provide a description that would be used to best describe the use case of the NumberInput component
     */
    ariaLabel: PropTypes.string,
    /**
     * `true` to use the light version.
     */
    light: PropTypes.bool,
    /**
     * `true` to allow empty string.
     */
    allowEmpty: PropTypes.bool,
    /**
     * Provide custom text for the component for each translation id
     */
    translateWithId: PropTypes.func.isRequired,
    /**
     * `true` to use the mobile variant.
     */
    isMobile: PropTypes.bool,
  };

  static defaultProps = {
    disabled: false,
    hideLabel: false,
    iconDescription: 'choose a number',
    label: ' ',
    step: 1,
    invalid: false,
    invalidText: 'Provide invalidText',
    ariaLabel: 'Numeric input field with increment and decrement buttons',
    helperText: '',
    light: false,
    allowEmpty: false,
    translateWithId: id => defaultTranslations[id],
  };

  static getDerivedStateFromProps({ min, max, value = 0 }, state) {
    const { prevValue } = state;

    if (useControlledStateWithValue && value === '' && prevValue !== '') {
      return {
        value: '',
        prevValue: '',
      };
    }

    // If `useControlledStateWithValue` feature flag is on, do nothing here.
    // Otherwise, do prop -> state sync with "value capping".
    return useControlledStateWithValue || prevValue === value
      ? null
      : {
          value: capMax(max, capMin(min, value)),
          prevValue: value,
        };
  }

  /**
   * The DOM node refernce to the `<input>`.
   * @type {HTMLInputElement}
   */
  _inputRef = null;

  constructor(props) {
    super(props);
    this.isControlled = props.value !== undefined;
    if (useControlledStateWithValue && this.isControlled) {
      // Skips the logic of setting initial state if this component is controlled
      return;
    }
    let value = useControlledStateWithValue ? props.defaultValue : props.value;
    value = value === undefined ? 0 : value;
    if (props.min || props.min === 0) {
      value = Math.max(props.min, value);
    }
    this.state = { value };
  }

  handleChange = evt => {
    const { disabled, onChange } = this.props;
    if (!disabled) {
      evt.persist();
      evt.imaginaryTarget = this._inputRef;
      const value = evt.target.value;
      this.setState(
        {
          value,
        },
        () => {
          if (useControlledStateWithValue) {
            onChange(evt, { value });
          } else if (onChange) {
            onChange(evt);
          }
        }
      );
    }
  };

  handleArrowClick = (evt, direction) => {
    let value =
      typeof this.state.value === 'string'
        ? Number(this.state.value)
        : this.state.value;
    const { disabled, min, max, step, onChange, onClick } = this.props;
    const conditional =
      direction === 'down'
        ? (min !== undefined && value > min) || min === undefined
        : (max !== undefined && value < max) || max === undefined;

    if (!disabled && conditional) {
      value = direction === 'down' ? value - step : value + step;
      value = capMax(max, capMin(min, value));
      evt.persist();
      evt.imaginaryTarget = this._inputRef;
      this.setState(
        {
          value,
        },
        () => {
          if (useControlledStateWithValue) {
            onClick && onClick(evt, { value, direction });
            onChange && onChange(evt, { value, direction });
          } else {
            onClick && onClick(evt, direction);
            onChange && onChange(evt, direction);
          }
        }
      );
    }
  };

  /**
   * Preserves the DOM node ref of `<input>`.
   * @param {HTMLInputElement} ref The DOM node ref of `<input>`.
   */
  _handleInputRef = ref => {
    this._inputRef = ref;
  };

  render() {
    const {
      className,
      disabled,
      iconDescription, // eslint-disable-line
      id,
      hideLabel,
      label,
      max,
      min,
      step,
      value,
      readOnly,
      invalid,
      invalidText,
      helperText,
      ariaLabel,
      light,
      allowEmpty,
      innerRef: ref,
      translateWithId: t,
      isMobile,
      ...other
    } = this.props;

    const numberInputClasses = classNames(
      `${prefix}--number ${prefix}--number--helpertext`,
      className,
      {
        [`${prefix}--number--readonly`]: readOnly,
        [`${prefix}--number--light`]: light,
        [`${prefix}--number--nolabel`]: hideLabel,
        [`${prefix}--number--mobile`]: isMobile,
      }
    );

    const props = {
      disabled,
      id,
      max,
      min,
      step,
      onChange: this.handleChange,
      value:
        useControlledStateWithValue && this.isControlled
          ? value
          : this.state.value,
      readOnly,
      'aria-label': label ? null : ariaLabel,
    };

    const buttonProps = {
      disabled,
      type: 'button',
    };

    const inputWrapperProps = {};
    let errorId = null;
    let error = null;

    let isInputInvalid;

    // If the user supplied `invalid` through props, we'll defer to the passed in value
    if (invalid) {
      isInputInvalid = true;
    } else {
      // Otherwise, if we don't allow an empty value then we check to see
      // if the value is empty, or if it is out of range
      if (!allowEmpty && this.state.value === '') {
        isInputInvalid = true;
      } else {
        if (
          this.state.value !== '' &&
          (this.state.value > max || this.state.value < min)
        ) {
          isInputInvalid = true;
        }
      }
    }

    if (isInputInvalid) {
      inputWrapperProps['data-invalid'] = true;
      errorId = `${id}-error-id`;
      error = (
        <div className={`${prefix}--form-requirement`} id={errorId}>
          {invalidText}

      );
    }

    const helper = helperText ? (
      <div className={`${prefix}--form__helper-text`}>{helperText}

    ) : null;

    const labelClasses = classNames(`${prefix}--label`, {
      [`${prefix}--visually-hidden`]: hideLabel,
    });

    const labelText = label ? (

        {label}

    ) : null;

    const [incrementNumLabel, decrementNumLabel] = [
      t('increment.number'),
      t('decrement.number'),
    ];

    return (
      <div className={`${prefix}--form-item`}>

          {(() => {
            if (isMobile) {
              return (
                <>
                  {labelText}
                  {helper}
                  <div className={`${prefix}--number__input-wrapper`}>
                    <button
                      className={`${prefix}--number__control-btn down-icon`}
                      {...buttonProps}
                      onClick={evt => this.handleArrowClick(evt, 'down')}
                      title={decrementNumLabel}
                      aria-label={decrementNumLabel || iconDescription}
                      aria-live="polite"
                      aria-atomic="true">
                      <CaretDownGlyph className="down-icon" />
                    </button>
                    <input
                      type="number"
                      pattern="[0-9]*"
                      {...other}
                      {...props}
                      ref={mergeRefs(ref, this._handleInputRef)}
                    />
                    <button
                      className={`${prefix}--number__control-btn up-icon`}
                      {...buttonProps}
                      onClick={evt => this.handleArrowClick(evt, 'up')}
                      title={incrementNumLabel}
                      aria-label={incrementNumLabel || iconDescription}
                      aria-live="polite"
                      aria-atomic="true">
                      <CaretUpGlyph className="up-icon" />
                    </button>

                </>
              );
            }
            return (
              <>
                {labelText}
                {helper}
                <div className={`${prefix}--number__input-wrapper`}>
                  <input
                    data-invalid={isInputInvalid}
                    aria-invalid={isInputInvalid}
                    aria-describedby={errorId}
                    type="number"
                    pattern="[0-9]*"
                    {...other}
                    {...props}
                    ref={mergeRefs(ref, this._handleInputRef)}
                  />
                  {isInputInvalid && (
                    <WarningFilled16 className={`${prefix}--number__invalid`} />
                  )}
                  <div className={`${prefix}--number__controls`}>
                    <button
                      className={`${prefix}--number__control-btn up-icon`}
                      {...buttonProps}
                      onClick={evt => this.handleArrowClick(evt, 'up')}
                      title={incrementNumLabel || iconDescription}
                      aria-label={incrementNumLabel || iconDescription}
                      aria-live="polite"
                      aria-atomic="true">
                      <CaretUpGlyph className="up-icon" />
                    </button>
                    <button
                      className={`${prefix}--number__control-btn down-icon`}
                      {...buttonProps}
                      onClick={evt => this.handleArrowClick(evt, 'down')}
                      title={decrementNumLabel || iconDescription}
                      aria-label={decrementNumLabel || iconDescription}
                      aria-live="polite"
                      aria-atomic="true">
                      <CaretDownGlyph className="down-icon" />
                    </button>

              </>
            );
          })()}
          {error}

    );
  }
}

export default (() => {
  const forwardRef = (props, ref) => <NumberInput {...props} innerRef={ref} />;
  forwardRef.displayName = 'NumberInput';
  return React.forwardRef(forwardRef);
})();
<commit_msg>fix(number-input): remove default value for label (#5347)<commit_after>/**
 * Copyright IBM Corp. 2016, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

import PropTypes from 'prop-types';
import React, { Component } from 'react';
import classNames from 'classnames';
import { settings } from 'carbon-components';
import {
  WarningFilled16,
  CaretDownGlyph,
  CaretUpGlyph,
} from '@carbon/icons-react';
import mergeRefs from '../../tools/mergeRefs';
import requiredIfValueExists from '../../prop-types/requiredIfValueExists';
import { useControlledStateWithValue } from '../../internal/FeatureFlags';

const { prefix } = settings;

export const translationIds = {
  'increment.number': 'increment.number',
  'decrement.number': 'decrement.number',
};

const defaultTranslations = {
  [translationIds['increment.number']]: 'Increment number',
  [translationIds['decrement.number']]: 'Decrement number',
};

const capMin = (min, value) =>
  isNaN(min) || (!min && min !== 0) || isNaN(value) || (!value && value !== 0)
    ? value
    : Math.max(min, value);
const capMax = (max, value) =>
  isNaN(max) || (!max && max !== 0) || isNaN(value) || (!value && value !== 0)
    ? value
    : Math.min(max, value);

class NumberInput extends Component {
  static propTypes = {
    /**
     * Specify an optional className to be applied to the wrapper node
     */
    className: PropTypes.string,
    /**
     * Specify if the control should be disabled, or not
     */
    disabled: PropTypes.bool,
    /**
     * Specify whether you want the underlying label to be visually hidden
     */
    hideLabel: PropTypes.bool,
    /**
     * Provide a description for up/down icons that can be read by screen readers
     */
    iconDescription: PropTypes.string.isRequired,
    /**
     * Specify a custom `id` for the input
     */
    id: PropTypes.string.isRequired,
    /**
     * Generic `label` that will be used as the textual representation of what
     * this field is for
     */
    label: PropTypes.node,
    /**
     * The maximum value.
     */
    max: PropTypes.number,
    /**
     * The minimum value.
     */
    min: PropTypes.number,
    /**
     * The new value is available in 'imaginaryTarget.value'
     * i.e. to get the value: evt.imaginaryTarget.value
     *
     * * _With_ `useControlledStateWithValue` feature flag, the signature of the event handler will be altered to provide additional context in the second parameter: `onChange(event, { value, direction })` where:
     *   * `event` is the (React) raw event
     *   * `value` is the new value
     *   * `direction` tells you the button you hit is up button or down button
     * * _Without_ this feature flag the event handler has `onChange(event, direction)` signature.
     */
    onChange: !useControlledStateWithValue
      ? PropTypes.func
      : requiredIfValueExists(PropTypes.func),
    /**
     * Provide an optional function to be called when the up/down button is clicked
     */
    onClick: PropTypes.func,
    /**
     * Specify how much the valus should increase/decrease upon clicking on up/down button
     */
    step: PropTypes.number,
    /**
     * Optional starting value for uncontrolled state
     */
    defaultValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    /**
     * Specify the value of the input
     */
    value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    /**
     * Specify if the component should be read-only
     */
    readOnly: PropTypes.bool,
    /**
     * Specify if the currently value is invalid.
     */
    invalid: PropTypes.bool,
    /**
     * Message which is displayed if the value is invalid.
     */
    invalidText: PropTypes.string,
    /**
     * Provide text that is used alongside the control label for additional help
     */
    helperText: PropTypes.node,
    /**
     * Provide a description that would be used to best describe the use case of the NumberInput component
     */
    ariaLabel: PropTypes.string,
    /**
     * `true` to use the light version.
     */
    light: PropTypes.bool,
    /**
     * `true` to allow empty string.
     */
    allowEmpty: PropTypes.bool,
    /**
     * Provide custom text for the component for each translation id
     */
    translateWithId: PropTypes.func.isRequired,
    /**
     * `true` to use the mobile variant.
     */
    isMobile: PropTypes.bool,
  };

  static defaultProps = {
    disabled: false,
    hideLabel: false,
    iconDescription: 'choose a number',
    step: 1,
    invalid: false,
    invalidText: 'Provide invalidText',
    ariaLabel: 'Numeric input field with increment and decrement buttons',
    helperText: '',
    light: false,
    allowEmpty: false,
    translateWithId: id => defaultTranslations[id],
  };

  static getDerivedStateFromProps({ min, max, value = 0 }, state) {
    const { prevValue } = state;

    if (useControlledStateWithValue && value === '' && prevValue !== '') {
      return {
        value: '',
        prevValue: '',
      };
    }

    // If `useControlledStateWithValue` feature flag is on, do nothing here.
    // Otherwise, do prop -> state sync with "value capping".
    return useControlledStateWithValue || prevValue === value
      ? null
      : {
          value: capMax(max, capMin(min, value)),
          prevValue: value,
        };
  }

  /**
   * The DOM node refernce to the `<input>`.
   * @type {HTMLInputElement}
   */
  _inputRef = null;

  constructor(props) {
    super(props);
    this.isControlled = props.value !== undefined;
    if (useControlledStateWithValue && this.isControlled) {
      // Skips the logic of setting initial state if this component is controlled
      return;
    }
    let value = useControlledStateWithValue ? props.defaultValue : props.value;
    value = value === undefined ? 0 : value;
    if (props.min || props.min === 0) {
      value = Math.max(props.min, value);
    }
    this.state = { value };
  }

  handleChange = evt => {
    const { disabled, onChange } = this.props;
    if (!disabled) {
      evt.persist();
      evt.imaginaryTarget = this._inputRef;
      const value = evt.target.value;
      this.setState(
        {
          value,
        },
        () => {
          if (useControlledStateWithValue) {
            onChange(evt, { value });
          } else if (onChange) {
            onChange(evt);
          }
        }
      );
    }
  };

  handleArrowClick = (evt, direction) => {
    let value =
      typeof this.state.value === 'string'
        ? Number(this.state.value)
        : this.state.value;
    const { disabled, min, max, step, onChange, onClick } = this.props;
    const conditional =
      direction === 'down'
        ? (min !== undefined && value > min) || min === undefined
        : (max !== undefined && value < max) || max === undefined;

    if (!disabled && conditional) {
      value = direction === 'down' ? value - step : value + step;
      value = capMax(max, capMin(min, value));
      evt.persist();
      evt.imaginaryTarget = this._inputRef;
      this.setState(
        {
          value,
        },
        () => {
          if (useControlledStateWithValue) {
            onClick && onClick(evt, { value, direction });
            onChange && onChange(evt, { value, direction });
          } else {
            onClick && onClick(evt, direction);
            onChange && onChange(evt, direction);
          }
        }
      );
    }
  };

  /**
   * Preserves the DOM node ref of `<input>`.
   * @param {HTMLInputElement} ref The DOM node ref of `<input>`.
   */
  _handleInputRef = ref => {
    this._inputRef = ref;
  };

  render() {
    const {
      className,
      disabled,
      iconDescription, // eslint-disable-line
      id,
      hideLabel,
      label,
      max,
      min,
      step,
      value,
      readOnly,
      invalid,
      invalidText,
      helperText,
      ariaLabel,
      light,
      allowEmpty,
      innerRef: ref,
      translateWithId: t,
      isMobile,
      ...other
    } = this.props;

    const numberInputClasses = classNames(
      `${prefix}--number ${prefix}--number--helpertext`,
      className,
      {
        [`${prefix}--number--readonly`]: readOnly,
        [`${prefix}--number--light`]: light,
        [`${prefix}--number--nolabel`]: hideLabel,
        [`${prefix}--number--mobile`]: isMobile,
      }
    );

    const props = {
      disabled,
      id,
      max,
      min,
      step,
      onChange: this.handleChange,
      value:
        useControlledStateWithValue && this.isControlled
          ? value
          : this.state.value,
      readOnly,
      'aria-label': label ? null : ariaLabel,
    };

    const buttonProps = {
      disabled,
      type: 'button',
    };

    const inputWrapperProps = {};
    let errorId = null;
    let error = null;

    let isInputInvalid;

    // If the user supplied `invalid` through props, we'll defer to the passed in value
    if (invalid) {
      isInputInvalid = true;
    } else {
      // Otherwise, if we don't allow an empty value then we check to see
      // if the value is empty, or if it is out of range
      if (!allowEmpty && this.state.value === '') {
        isInputInvalid = true;
      } else {
        if (
          this.state.value !== '' &&
          (this.state.value > max || this.state.value < min)
        ) {
          isInputInvalid = true;
        }
      }
    }

    if (isInputInvalid) {
      inputWrapperProps['data-invalid'] = true;
      errorId = `${id}-error-id`;
      error = (
        <div className={`${prefix}--form-requirement`} id={errorId}>
          {invalidText}

      );
    }

    const helper = helperText ? (
      <div className={`${prefix}--form__helper-text`}>{helperText}

    ) : null;

    const labelClasses = classNames(`${prefix}--label`, {
      [`${prefix}--visually-hidden`]: hideLabel,
    });

    const labelText = label ? (

        {label}

    ) : null;

    const [incrementNumLabel, decrementNumLabel] = [
      t('increment.number'),
      t('decrement.number'),
    ];

    return (
      <div className={`${prefix}--form-item`}>

          {(() => {
            if (isMobile) {
              return (
                <>
                  {labelText}
                  {helper}
                  <div className={`${prefix}--number__input-wrapper`}>
                    <button
                      className={`${prefix}--number__control-btn down-icon`}
                      {...buttonProps}
                      onClick={evt => this.handleArrowClick(evt, 'down')}
                      title={decrementNumLabel}
                      aria-label={decrementNumLabel || iconDescription}
                      aria-live="polite"
                      aria-atomic="true">
                      <CaretDownGlyph className="down-icon" />
                    </button>
                    <input
                      type="number"
                      pattern="[0-9]*"
                      {...other}
                      {...props}
                      ref={mergeRefs(ref, this._handleInputRef)}
                    />
                    <button
                      className={`${prefix}--number__control-btn up-icon`}
                      {...buttonProps}
                      onClick={evt => this.handleArrowClick(evt, 'up')}
                      title={incrementNumLabel}
                      aria-label={incrementNumLabel || iconDescription}
                      aria-live="polite"
                      aria-atomic="true">
                      <CaretUpGlyph className="up-icon" />
                    </button>

                </>
              );
            }
            return (
              <>
                {labelText}
                {helper}
                <div className={`${prefix}--number__input-wrapper`}>
                  <input
                    data-invalid={isInputInvalid}
                    aria-invalid={isInputInvalid}
                    aria-describedby={errorId}
                    type="number"
                    pattern="[0-9]*"
                    {...other}
                    {...props}
                    ref={mergeRefs(ref, this._handleInputRef)}
                  />
                  {isInputInvalid && (
                    <WarningFilled16 className={`${prefix}--number__invalid`} />
                  )}
                  <div className={`${prefix}--number__controls`}>
                    <button
                      className={`${prefix}--number__control-btn up-icon`}
                      {...buttonProps}
                      onClick={evt => this.handleArrowClick(evt, 'up')}
                      title={incrementNumLabel || iconDescription}
                      aria-label={incrementNumLabel || iconDescription}
                      aria-live="polite"
                      aria-atomic="true">
                      <CaretUpGlyph className="up-icon" />
                    </button>
                    <button
                      className={`${prefix}--number__control-btn down-icon`}
                      {...buttonProps}
                      onClick={evt => this.handleArrowClick(evt, 'down')}
                      title={decrementNumLabel || iconDescription}
                      aria-label={decrementNumLabel || iconDescription}
                      aria-live="polite"
                      aria-atomic="true">
                      <CaretDownGlyph className="down-icon" />
                    </button>

              </>
            );
          })()}
          {error}

    );
  }
}

export default (() => {
  const forwardRef = (props, ref) => <NumberInput {...props} innerRef={ref} />;
  forwardRef.displayName = 'NumberInput';
  return React.forwardRef(forwardRef);
})();
<|endoftext|>


<commit_before>/* global describe, it, before */
/* eslint-disable import/no-extraneous-dependencies, func-names */
const chai = require('chai');
const join = require('path').join;
const execSync = require('child_process').execSync;
const fs = require('fs');

const assert = chai.assert;
chai.use(require('chai-fs'));

describe('Sass Export Data', function () {
  this.timeout(5000);
  let output = '';
  before(() => {
    try {
      output = execSync('npm run test:compile', {
        cwd: __dirname,
        encoding: 'utf8',
      });
      // console.log(output);
    } catch (e) {
      console.error('Could not run command beforehand.', e);
    }
  });

  fs.readdirSync(join(__dirname, './basics/expected'))
    .filter(file => file.endsWith('json'))
    .forEach((file) => {
      it(`Creates JSON from Sass Vars in ${file}`, () => {
        assert.deepEqual(
          JSON.parse(
            fs.readFileSync(
              join(__dirname, './basics/dest/', file),
              'utf8',
            )
          ),
          JSON.parse(
            fs.readFileSync(
              join(__dirname, './basics/expected/', file),
              'utf8',
            )
          ),
          `JSON files do not match.\n\n${output}`
        );
      });
    });
});
<commit_msg>Fixing trailing comma in function params for Node 6<commit_after>/* global describe, it, before */
/* eslint-disable import/no-extraneous-dependencies, func-names */
const chai = require('chai');
const join = require('path').join;
const execSync = require('child_process').execSync;
const fs = require('fs');

const assert = chai.assert;
chai.use(require('chai-fs'));

describe('Sass Export Data', function () {
  this.timeout(5000);
  let output = '';
  before(() => {
    try {
      output = execSync('npm run test:compile', {
        cwd: __dirname,
        encoding: 'utf8',
      });
      // console.log(output);
    } catch (e) {
      console.error('Could not run command beforehand.', e);
    }
  });

  fs.readdirSync(join(__dirname, './basics/expected'))
    .filter(file => file.endsWith('json'))
    .forEach((file) => {
      it(`Creates JSON from Sass Vars in ${file}`, () => {
        assert.deepEqual(
          JSON.parse(
            fs.readFileSync(
              join(__dirname, './basics/dest/', file),
              'utf8'
            )
          ),
          JSON.parse(
            fs.readFileSync(
              join(__dirname, './basics/expected/', file),
              'utf8',
            )
          ),
          `JSON files do not match.\n\n${output}`
        );
      });
    });
});
<|endoftext|>


<commit_before>import markdown from 'markdown-in-js'
import withDoc, { components } from '~/lib/with-doc'

import { expoteam } from '~/data/team'
import JSFeaturesTable from '~/components/js-features-table'

// prettier-ignore
export default withDoc({
  title: 'Using Modern JavaScript',

  authors: [expoteam],
})(markdown(components)`

JavaScript is an evolving language. You may have heard of ES2015, ES2016, and so on, which are new versions of JavaScript that have added features like classes, async functions, and more.

You can use much of modern JavaScript with Expo and React Native. The JavaScript VMs on devices already support many modern features. The JavaScript packager ([Metro Bundler](https://github.com/facebook/metro-bundler)) also uses Babel to transform modern JavaScript into an older version that JavaScript VMs can interpret. Between newer JavaScript VMs and Babel, you can use many features of modern JavaScript.

## JavaScript VMs and Expo

On iOS, Expo uses the JavaScriptCore VM that is part of iOS. This means that different versions of iOS have different versions of JavaScriptCore. The newer versions support more features.

On Android, Expo includes its own copy of JavaScriptCore. This means that Expo projects have the same JavaScript features across all Android devices. Expo's version of JavaScriptCore is similar to that included with iOS 10.3.

The table later on this page tells you which features Expo supports. [Kangax's compatibility tables](https://kangax.github.io/compat-table/) also are a helpful resource to see which versions of Safari, which uses JavaScriptCore, support which features of modern JavaScript.

## Polyfills and Babel Transforms

Some parts of the modern JavaScript API are implemented in polyfills. These polyfills are included in your JavaScript bundle by Metro.

Before your code runs in your app, Metro also transforms your code and your dependencies with Babel. By default, Expo projects define a preset Babel configuration (babel-preset-expo) based on the React Native preset ([babel-preset-react-native](https://github.com/facebook/react-native/tree/master/babel-preset)).

## JavaScript Support in Expo

${<JSFeaturesTable />}

## WebAssembly

WebAssembly, or WASM for short, is a low-level language implemented in JavaScript interpreters. WebAssembly is not JavaScript, but WebAssembly code can interact with JavaScript code in the same program. Currently, Expo does not support WebAssembly.

`)
<commit_msg>Fix bad import<commit_after>import markdown from 'markdown-in-js'
import withDoc, { components } from '~/lib/with-doc'

import { expoteam } from '~/data/team'
import JSFeaturesTable from '~/components/plugins/js-features-table'

// prettier-ignore
export default withDoc({
  title: 'Using Modern JavaScript',

  authors: [expoteam],
})(markdown(components)`

JavaScript is an evolving language. You may have heard of ES2015, ES2016, and so on, which are new versions of JavaScript that have added features like classes, async functions, and more.

You can use much of modern JavaScript with Expo and React Native. The JavaScript VMs on devices already support many modern features. The JavaScript packager ([Metro Bundler](https://github.com/facebook/metro-bundler)) also uses Babel to transform modern JavaScript into an older version that JavaScript VMs can interpret. Between newer JavaScript VMs and Babel, you can use many features of modern JavaScript.

## JavaScript VMs and Expo

On iOS, Expo uses the JavaScriptCore VM that is part of iOS. This means that different versions of iOS have different versions of JavaScriptCore. The newer versions support more features.

On Android, Expo includes its own copy of JavaScriptCore. This means that Expo projects have the same JavaScript features across all Android devices. Expo's version of JavaScriptCore is similar to that included with iOS 10.3.

The table later on this page tells you which features Expo supports. [Kangax's compatibility tables](https://kangax.github.io/compat-table/) also are a helpful resource to see which versions of Safari, which uses JavaScriptCore, support which features of modern JavaScript.

## Polyfills and Babel Transforms

Some parts of the modern JavaScript API are implemented in polyfills. These polyfills are included in your JavaScript bundle by Metro.

Before your code runs in your app, Metro also transforms your code and your dependencies with Babel. By default, Expo projects define a preset Babel configuration (babel-preset-expo) based on the React Native preset ([babel-preset-react-native](https://github.com/facebook/react-native/tree/master/babel-preset)).

## JavaScript Support in Expo

${<JSFeaturesTable />}

## WebAssembly

WebAssembly, or WASM for short, is a low-level language implemented in JavaScript interpreters. WebAssembly is not JavaScript, but WebAssembly code can interact with JavaScript code in the same program. Currently, Expo does not support WebAssembly.

`)
<|endoftext|>


<commit_before>var async = require('async'),
    express = require("express"),
    mongoose = require("mongoose"),
    router = express.Router(),
    Auth = require("../../controllers/auth"),
    Error = require("../../controllers/error"),
    MasterController = require("../../controllers/master"),
    entities = require("../entityConfig"),
    epoch = require("milli-epoch"),
    Notifier = require("../../controllers/notifier"),
    git = require("github"),
    atob = require("atob"),
    Config = require("../../../config"),
    GitHub = new git({
        // required
        version: "3.0.0",
        // optional
        debug: false,
        protocol: "https",
        host: "api.github.com", // should be api.github.com for GitHub
        pathPrefix: "", // for some GHEs; none for GitHub
        timeout: 5000,
        headers: {
            "user-agent": "qlik-branch" // GitHub is happy with a unique user agent
        }
    });

//load in the functions
var createupdate = require("./createupdate");
var read = require("./read");
var update = require("./update");
var flag = require("./flag");
var hide = require("./hide");
var approve = require("./approve");
var get = require("./get")

GitHub.authenticate({type: "token", token: Config.git.token });

//This route is for getting a list of results for the specified entity
//url parameters can be used to add filtering
//Requires "read" permission on the specified entity
router.get("/:entity", Auth.isLoggedIn, function(req, res, next){
  var queryObj = parseQuery(req.query || {}, req.body || {}, "GET", entities[req.params.entity]);
  var query = queryObj.query;
  var entity = queryObj.entity;
  var user = req.user;
  var userPermissions;
  if(req.user){
    userPermissions = req.user.role.permissions[req.params.entity];
  }
  //check that the user has sufficient permissions for this operation
  if((!userPermissions || userPermissions.read!=true) && entity.requiresAuthentication){
    res.json(Error.insufficientPermissions("Unable to get "+req.params.entity));
  }
  else{
    // if((userPermissions && userPermissions.allOwners!=true) && entity.exemptFromOwnership!=true && !entity.requiresAuthentication){
    //   query["createuser"]=user._id;
    // }

    //add filter for approved items
    if((userPermissions && userPermissions.approve!=true && entity.exemptFromApproval!=true)
        || (!user && entity.exemptFromApproval!=true)){
          query.$or = [];
          query.$or.push({approved:true});
          if(user){
            query.$or.push({userid: user["_id"] });
          }
    }
    MasterController.get(req.query, query, entity, function(results){
      res.json(results || {});
    });
  }
});

//This route is for getting a count of results for the specified entity
//url parameters can be used to add filtering
//Requires "read" permission on the specified entity
router.get("/:entity/count", Auth.isLoggedIn, function(req, res){
  var queryObj = parseQuery(req.query || {}, req.body || {}, "GET", entities[req.params.entity]);
  var query = queryObj.query;
  var entity = queryObj.entity;
  var user = req.user;
  var userPermissions;
  //check that the user has sufficient permissions for this operation
  if(req.user){
    userPermissions = req.user.role.permissions[req.params.entity];
  }
  //check that the user has sufficient permissions for this operation
  if((!userPermissions || userPermissions.read!=true) && entity.requiresAuthentication){
    res.json(Error.insufficientPermissions("Unable to count "+req.params.entity));
  }
  else{
    // if((userPermissions && userPermissions.allOwners!=true) && entity.exemptFromOwnership!=true && !entity.requiresAuthentication){
    //   query["createuser"]=user._id;
    // }
    if((userPermissions && userPermissions.approve!=true && entity.exemptFromApproval!=true)
        || (!user)){
          query.$or = [];
          query.$or.push({approved:true});
          if(user){
            query.$or.push({userid: user["_id"] });
          }
    }
    MasterController.count(req.query, query, entity, function(results){
      res.json(results);
    });
  }
});

router.get("/:entity/hidden", Auth.isLoggedIn, read.getHidden);
router.get("/:entity/flagged", Auth.isLoggedIn, read.getFlagged);

//This route is for getting a specific result from the specified entity
//url parameters can be used to add filtering
//Requires "read" permission on the specified entity
//If the requested entity is "projects" then we check first to see if the latest
//data has been fetched from git within the last hour. If not we need to update the db
//with the latest data from git. Otherwise we return the document from mongo
router.get("/:entity/:id", Auth.isLoggedIn, function(req, res){
  var queryObj = parseQuery(req.query || {}, req.body || {}, "GET", entities[req.params.entity]);
  var query = queryObj.query;
  var entity = queryObj.entity;
  query["_id"] = req.params.id;
  var user = req.user;
  var userPermissions;
  //check that the user has sufficient permissions for this operation
  if(req.user){
    userPermissions = req.user.role.permissions[req.params.entity];
  }
  //check that the user has sufficient permissions for this operation
  if((!userPermissions || userPermissions.read!=true) && entity.requiresAuthentication){
    res.json(Error.insufficientPermissions("Unable to get "+req.params.entity));
  }
  else{
    // if((userPermissions && userPermissions.allOwners!=true) && entity.exemptFromOwnership!=true && !entity.requiresAuthentication){
    //   query["createuser"]=user._id;
    // }
    if((userPermissions && userPermissions.approve!=true && entity.exemptFromApproval!=true)
        || (!user)){
      query.$or = [];
      query.$or.push({approved:true});
      if(user){
        query.$or.push({userid: user["_id"] });
      }
    }
    MasterController.get(req.query, query, entity, function(results){
      if(entity.logViews){
        //check to see if the current user or IP Address has viewed the same item
        //in the last hour. If not add an item to the views table
        var anHourAgo = (new Date()).getTime() - (360000);
        var ip = -1;
        if(req.ip){
          ip = req.ip.split(":").pop();
        }
        var viewQuery = {
          createdate: {
            $gt: anHourAgo
          },
          entityId: req.params.id
        };
        if(req.user){
          viewQuery.userid = req.user._id;
        }
        else{
          viewQuery.ip = ip;
        }
        MasterController.get(viewQuery, viewQuery, entities["view"], function(results){
          if(results.data && results.data.length == 0){
            var viewData = {};
            if(req.user){
              viewData.userid = req.user._id;
            }
            viewData.ip = ip;
            viewData.entityId = req.params.id;
            MasterController.save(null, viewData, entities["view"], function(result){

            });
          }
        });

      }
      if(req.params.entity=="project"&&(results.data && results.data[0] && results.data[0].git_repo && ((results.data[0].last_git_check && results.data[0].last_git_check.getTime() < anHourAgo)||(!results.data[0].last_git_check)))){
        //if we're here then the following criteria has been met
        // - entity == "projects"
        // - project has a project_site
        // - the git details have not been updated for an hour +
        var repo = results.data[0].git_repo;
        var gituser = results.data[0].git_user;
        console.log(Config.git.token);
        GitHub.authenticate({type: "token", token: Config.git.token });
        GitHub.repos.get({user:gituser, repo:repo}, function(err, gitresult){
          if(err){
            res.json(Error.custom(err.message));
          }
          else{
            //update the update date and git check data
            var hasChanged = results.data[0].last_updated_num!=(new Date(gitresult.updated_at)).getTime();
            if(hasChanged){
              console.log('it has changed');
              results.data[0].last_updated = new Date(gitresult.updated_at);
              results.data[0].last_updated_num = (new Date(gitresult.updated_at)).getTime();
              results.data[0].last_git_check = Date.now();
              GitHub.authenticate({type: "token", token: Config.git.token });
              GitHub.repos.getReadme({user:gituser, repo:repo, headers:{accept: 'application/vnd.github.VERSION.raw'}}, function(err, readmeresult){
                if(err){
                  console.log(err);
                }
                GitHub.authenticate({type: "token", token: Config.git.token });
                GitHub.markdown.renderRaw({data: readmeresult, mode: 'markdown'}, function(err, htmlresult){
                  if(err){
                    console.log(err);
                  }
                  else{
                    htmlresult = htmlresult.replace(/href="((?!http)[^>]*)"/gim, "href=\"https://github.com/"+gituser+"/"+repo+"/raw/master/$1\"")
                    htmlresult = htmlresult.replace(/src="((?!http)[^>]*)"/gim, "src=\"https://github.com/"+gituser+"/"+repo+"/raw/master/$1\"")
                    results.data[0].content = htmlresult;
                    results.data[0].save(function(err){
                      if(!err){
                        Notifier.sendUpdateNotification(results.data[0]._id, results.data[0], req.params.entity);
                      }
                    });
                  }
                  res.json(results || {});
                })
              });
            }
            else{
              res.json(results || {});
            }
          }
        });
      }
      else{
        res.json(results || {});
      }
    });
  }
});

//This route is for fetching the thumbnail image assigned to a given entity document
//Authnetication requirements to be defined
router.get('/:entity/:id/thumbnail', Auth.isLoggedIn, function(req, res){
  MasterController.getThumbnail({_id: req.params.id}, entities[req.params.entity], function(result){
    if(result){
      res.send(result.thumbnail);
    }
    else {
      res.send(Error.noRecord());
    }
  });
});

//router.post("/projects", Auth.isLoggedIn, create.createProject);
router.post("/:entity", Auth.isLoggedIn, createupdate);
//router.post("/projects/:id", Auth.isLoggedIn, update.updateProject);
router.post("/:entity/:id", Auth.isLoggedIn, createupdate);
router.post("/:entity/:id/flag", Auth.isLoggedIn, flag.flag);
router.post("/:entity/:id/unflag", Auth.isLoggedIn, flag.unflag);
router.post("/:entity/:id/hide", Auth.isLoggedIn, hide);
router.post("/:entity/:id/approve", Auth.isLoggedIn, approve);
router.post("/:entity/rating/my", Auth.isLoggedIn, get.getMyRating);

//This route is for deleting a list of records on the specified entity
//url parameters can be used to add filtering
//Requires "delete" permission on the specified entity
// router.delete("/:entity", Auth.isLoggedIn, function(req, res){
//   var query = req.query || {};
//   var entity = req.params.entity;
//   var user = req.user;
//   var userPermissions = req.user.role.permissions[entity];
//   if(!userPermissions || userPermissions.delete!=true){
//     res.json(Error.insufficientPermissions());
//   }
//   else{
//     if(userPermissions.allOwners!=true){
//       query["createuser"]=user._id;
//     }
//     MasterController.delete(query, entities[entity], function(result){
//       res.json(result);
//     });
//   }
// });

//This route is for deleting a specific record on the specified entity
//url parameters can be used to add filtering
//Requires "delete" permission on the specified entity
router.delete("/:entity/:id", Auth.isLoggedIn, function(req, res){
  var query = req.query || {};
  query["_id"] = req.params.id;
  var entity = req.params.entity;
  var user = req.user;
  var userPermissions = req.user.role.permissions[entity];
  if(!userPermissions || userPermissions.delete!=true){
    res.json(Error.insufficientPermissions());
  }
  else{
    if(userPermissions.allOwners!=true){
      query["userid"]=user._id;
    }
    MasterController.delete(query, entities[entity], function(result){
      //need to delete any comments and flags
      res.json(result);
    });
  }
});

//this function parses any sorting or paging parameters and contstructs the mongodb query accordingly.
//Currently only used for GET requests
function parseQuery(query, body, method, originalEntity){
  var entity = cloneObject(originalEntity);
  var mongoQuery = {};
  query = query || {};
  body = body || {};
  if(query.sort){
    var sortFields = query.sort.toString().split(",");
    var sortOrders = query.sortOrder.toString().split(",");
    var sort = {};
    for(var i=0; i < sortFields.length; i++){
      sort[sortFields[i]] = sortOrders[i] || 1;
    }
    entity.sort = sort;
    delete query["sort"];
    delete query["sortOrder"];
  }
  entity.skip = query.skip || entity.skip || 0;
  entity.limit = query.limit || entity.limit || 0;
  delete query["skip"];
  delete query["limit"];

  if(method=="GET"){
    query = concatObjects([query, body]);
  }

  mongoQuery.entity = entity;
  mongoQuery.query = query;

  return mongoQuery;
}

function cloneObject(object){
  var clone = {};
  for (var key in object){
    clone[key] = object[key];
  }
  return clone;
}

function concatObjects(objects){
  var result = {};
  for (var o in objects){
    for (var key in objects[o]) result[key]=objects[o][key];
  }
  return result;
}

module.exports = router;
<commit_msg>Switching from updated_at to pushed_at<commit_after>var async = require('async'),
    express = require("express"),
    mongoose = require("mongoose"),
    router = express.Router(),
    Auth = require("../../controllers/auth"),
    Error = require("../../controllers/error"),
    MasterController = require("../../controllers/master"),
    entities = require("../entityConfig"),
    epoch = require("milli-epoch"),
    Notifier = require("../../controllers/notifier"),
    git = require("github"),
    atob = require("atob"),
    Config = require("../../../config"),
    GitHub = new git({
        // required
        version: "3.0.0",
        // optional
        debug: false,
        protocol: "https",
        host: "api.github.com", // should be api.github.com for GitHub
        pathPrefix: "", // for some GHEs; none for GitHub
        timeout: 5000,
        headers: {
            "user-agent": "qlik-branch" // GitHub is happy with a unique user agent
        }
    });

//load in the functions
var createupdate = require("./createupdate");
var read = require("./read");
var update = require("./update");
var flag = require("./flag");
var hide = require("./hide");
var approve = require("./approve");
var get = require("./get")

GitHub.authenticate({type: "token", token: Config.git.token });

//This route is for getting a list of results for the specified entity
//url parameters can be used to add filtering
//Requires "read" permission on the specified entity
router.get("/:entity", Auth.isLoggedIn, function(req, res, next){
  var queryObj = parseQuery(req.query || {}, req.body || {}, "GET", entities[req.params.entity]);
  var query = queryObj.query;
  var entity = queryObj.entity;
  var user = req.user;
  var userPermissions;
  if(req.user){
    userPermissions = req.user.role.permissions[req.params.entity];
  }
  //check that the user has sufficient permissions for this operation
  if((!userPermissions || userPermissions.read!=true) && entity.requiresAuthentication){
    res.json(Error.insufficientPermissions("Unable to get "+req.params.entity));
  }
  else{
    // if((userPermissions && userPermissions.allOwners!=true) && entity.exemptFromOwnership!=true && !entity.requiresAuthentication){
    //   query["createuser"]=user._id;
    // }

    //add filter for approved items
    if((userPermissions && userPermissions.approve!=true && entity.exemptFromApproval!=true)
        || (!user && entity.exemptFromApproval!=true)){
          query.$or = [];
          query.$or.push({approved:true});
          if(user){
            query.$or.push({userid: user["_id"] });
          }
    }
    MasterController.get(req.query, query, entity, function(results){
      res.json(results || {});
    });
  }
});

//This route is for getting a count of results for the specified entity
//url parameters can be used to add filtering
//Requires "read" permission on the specified entity
router.get("/:entity/count", Auth.isLoggedIn, function(req, res){
  var queryObj = parseQuery(req.query || {}, req.body || {}, "GET", entities[req.params.entity]);
  var query = queryObj.query;
  var entity = queryObj.entity;
  var user = req.user;
  var userPermissions;
  //check that the user has sufficient permissions for this operation
  if(req.user){
    userPermissions = req.user.role.permissions[req.params.entity];
  }
  //check that the user has sufficient permissions for this operation
  if((!userPermissions || userPermissions.read!=true) && entity.requiresAuthentication){
    res.json(Error.insufficientPermissions("Unable to count "+req.params.entity));
  }
  else{
    // if((userPermissions && userPermissions.allOwners!=true) && entity.exemptFromOwnership!=true && !entity.requiresAuthentication){
    //   query["createuser"]=user._id;
    // }
    if((userPermissions && userPermissions.approve!=true && entity.exemptFromApproval!=true)
        || (!user)){
          query.$or = [];
          query.$or.push({approved:true});
          if(user){
            query.$or.push({userid: user["_id"] });
          }
    }
    MasterController.count(req.query, query, entity, function(results){
      res.json(results);
    });
  }
});

router.get("/:entity/hidden", Auth.isLoggedIn, read.getHidden);
router.get("/:entity/flagged", Auth.isLoggedIn, read.getFlagged);

//This route is for getting a specific result from the specified entity
//url parameters can be used to add filtering
//Requires "read" permission on the specified entity
//If the requested entity is "projects" then we check first to see if the latest
//data has been fetched from git within the last hour. If not we need to update the db
//with the latest data from git. Otherwise we return the document from mongo
router.get("/:entity/:id", Auth.isLoggedIn, function(req, res){
  var queryObj = parseQuery(req.query || {}, req.body || {}, "GET", entities[req.params.entity]);
  var query = queryObj.query;
  var entity = queryObj.entity;
  query["_id"] = req.params.id;
  var user = req.user;
  var userPermissions;
  //check that the user has sufficient permissions for this operation
  if(req.user){
    userPermissions = req.user.role.permissions[req.params.entity];
  }
  //check that the user has sufficient permissions for this operation
  if((!userPermissions || userPermissions.read!=true) && entity.requiresAuthentication){
    res.json(Error.insufficientPermissions("Unable to get "+req.params.entity));
  }
  else{
    // if((userPermissions && userPermissions.allOwners!=true) && entity.exemptFromOwnership!=true && !entity.requiresAuthentication){
    //   query["createuser"]=user._id;
    // }
    if((userPermissions && userPermissions.approve!=true && entity.exemptFromApproval!=true)
        || (!user)){
      query.$or = [];
      query.$or.push({approved:true});
      if(user){
        query.$or.push({userid: user["_id"] });
      }
    }
    MasterController.get(req.query, query, entity, function(results){
      if(entity.logViews){
        //check to see if the current user or IP Address has viewed the same item
        //in the last hour. If not add an item to the views table
        var anHourAgo = (new Date()).getTime() - (360000);
        var ip = -1;
        if(req.ip){
          ip = req.ip.split(":").pop();
        }
        var viewQuery = {
          createdate: {
            $gt: anHourAgo
          },
          entityId: req.params.id
        };
        if(req.user){
          viewQuery.userid = req.user._id;
        }
        else{
          viewQuery.ip = ip;
        }
        MasterController.get(viewQuery, viewQuery, entities["view"], function(results){
          if(results.data && results.data.length == 0){
            var viewData = {};
            if(req.user){
              viewData.userid = req.user._id;
            }
            viewData.ip = ip;
            viewData.entityId = req.params.id;
            MasterController.save(null, viewData, entities["view"], function(result){

            });
          }
        });

      }
      if(req.params.entity=="project"&&(results.data && results.data[0] && results.data[0].git_repo && ((results.data[0].last_git_check && results.data[0].last_git_check.getTime() < anHourAgo)||(!results.data[0].last_git_check)))){
        //if we're here then the following criteria has been met
        // - entity == "projects"
        // - project has a project_site
        // - the git details have not been updated for an hour +
        var repo = results.data[0].git_repo;
        var gituser = results.data[0].git_user;
        GitHub.authenticate({type: "token", token: Config.git.token });
        GitHub.repos.get({user:gituser, repo:repo}, function(err, gitresult){
          if(err){
            res.json(Error.custom(err.message));
          }
          else{
            //update the update date and git check data
            //Note: Using pushed_at instead of updated_at from Github to fetch latest commit date.
            //TODO <akl@qlik.com>: Only trigger on updates to the master branch.
            var hasChanged = results.data[0].last_updated_num!=(new Date(gitresult.pushed_at)).getTime();
            if(hasChanged){
              results.data[0].last_updated = new Date(gitresult.pushed_at);
              results.data[0].last_updated_num = (new Date(gitresult.pushed_at)).getTime();
              results.data[0].last_git_check = Date.now();
              GitHub.authenticate({type: "token", token: Config.git.token });
              GitHub.repos.getReadme({user:gituser, repo:repo, headers:{accept: 'application/vnd.github.VERSION.raw'}}, function(err, readmeresult){
                if(err){
                  console.log(err);
                }
                GitHub.authenticate({type: "token", token: Config.git.token });
                GitHub.markdown.renderRaw({data: readmeresult, mode: 'markdown'}, function(err, htmlresult){
                  if(err){
                    console.log(err);
                  }
                  else{
                    htmlresult = htmlresult.replace(/href="((?!http)[^>]*)"/gim, "href=\"https://github.com/"+gituser+"/"+repo+"/raw/master/$1\"")
                    htmlresult = htmlresult.replace(/src="((?!http)[^>]*)"/gim, "src=\"https://github.com/"+gituser+"/"+repo+"/raw/master/$1\"")
                    results.data[0].content = htmlresult;
                    results.data[0].save(function(err){
                      if(!err){
                        Notifier.sendUpdateNotification(results.data[0]._id, results.data[0], req.params.entity);
                      }
                    });
                  }
                  res.json(results || {});
                })
              });
            }
            else{
              res.json(results || {});
            }
          }
        });
      }
      else{
        res.json(results || {});
      }
    });
  }
});

//This route is for fetching the thumbnail image assigned to a given entity document
//Authnetication requirements to be defined
router.get('/:entity/:id/thumbnail', Auth.isLoggedIn, function(req, res){
  MasterController.getThumbnail({_id: req.params.id}, entities[req.params.entity], function(result){
    if(result){
      res.send(result.thumbnail);
    }
    else {
      res.send(Error.noRecord());
    }
  });
});

//router.post("/projects", Auth.isLoggedIn, create.createProject);
router.post("/:entity", Auth.isLoggedIn, createupdate);
//router.post("/projects/:id", Auth.isLoggedIn, update.updateProject);
router.post("/:entity/:id", Auth.isLoggedIn, createupdate);
router.post("/:entity/:id/flag", Auth.isLoggedIn, flag.flag);
router.post("/:entity/:id/unflag", Auth.isLoggedIn, flag.unflag);
router.post("/:entity/:id/hide", Auth.isLoggedIn, hide);
router.post("/:entity/:id/approve", Auth.isLoggedIn, approve);
router.post("/:entity/rating/my", Auth.isLoggedIn, get.getMyRating);

//This route is for deleting a list of records on the specified entity
//url parameters can be used to add filtering
//Requires "delete" permission on the specified entity
// router.delete("/:entity", Auth.isLoggedIn, function(req, res){
//   var query = req.query || {};
//   var entity = req.params.entity;
//   var user = req.user;
//   var userPermissions = req.user.role.permissions[entity];
//   if(!userPermissions || userPermissions.delete!=true){
//     res.json(Error.insufficientPermissions());
//   }
//   else{
//     if(userPermissions.allOwners!=true){
//       query["createuser"]=user._id;
//     }
//     MasterController.delete(query, entities[entity], function(result){
//       res.json(result);
//     });
//   }
// });

//This route is for deleting a specific record on the specified entity
//url parameters can be used to add filtering
//Requires "delete" permission on the specified entity
router.delete("/:entity/:id", Auth.isLoggedIn, function(req, res){
  var query = req.query || {};
  query["_id"] = req.params.id;
  var entity = req.params.entity;
  var user = req.user;
  var userPermissions = req.user.role.permissions[entity];
  if(!userPermissions || userPermissions.delete!=true){
    res.json(Error.insufficientPermissions());
  }
  else{
    if(userPermissions.allOwners!=true){
      query["userid"]=user._id;
    }
    MasterController.delete(query, entities[entity], function(result){
      //need to delete any comments and flags
      res.json(result);
    });
  }
});

//this function parses any sorting or paging parameters and contstructs the mongodb query accordingly.
//Currently only used for GET requests
function parseQuery(query, body, method, originalEntity){
  var entity = cloneObject(originalEntity);
  var mongoQuery = {};
  query = query || {};
  body = body || {};
  if(query.sort){
    var sortFields = query.sort.toString().split(",");
    var sortOrders = query.sortOrder.toString().split(",");
    var sort = {};
    for(var i=0; i < sortFields.length; i++){
      sort[sortFields[i]] = sortOrders[i] || 1;
    }
    entity.sort = sort;
    delete query["sort"];
    delete query["sortOrder"];
  }
  entity.skip = query.skip || entity.skip || 0;
  entity.limit = query.limit || entity.limit || 0;
  delete query["skip"];
  delete query["limit"];

  if(method=="GET"){
    query = concatObjects([query, body]);
  }

  mongoQuery.entity = entity;
  mongoQuery.query = query;

  return mongoQuery;
}

function cloneObject(object){
  var clone = {};
  for (var key in object){
    clone[key] = object[key];
  }
  return clone;
}

function concatObjects(objects){
  var result = {};
  for (var o in objects){
    for (var key in objects[o]) result[key]=objects[o][key];
  }
  return result;
}

module.exports = router;
<|endoftext|>