<commit_before>/**
 * The main colorful square for selecting color shades by dragging around the
 * little circle.
 */
Ext.define('Ext.ux.colorpick.ColorMap', {
    extend     : 'Ext.container.Container',
    alias      : 'widget.colorpickercolormap',
    controller : 'colorpickercolormapcontroller',

    requires: [
        'Ext.ux.colorpick.ColorMapController'
    ],

    cls  : 'x-colorpicker-colormap',

    // This is the drag "circle"; note it's 1x1 in size to allow full
    // travel around the color map; the inner div has the bigger image
    items: [{
        xtype     : 'component',
        cls       : 'x-colorpicker-colormap-draghandle-container',
        itemId    : 'dragHandle',
        width     : 1,
        height    : 1,
        draggable : true,
        html: '

'
    }],

    listeners : {
        boxready : {
            single  : true,
            fn      : 'onFirstBoxReady',
            scope   : 'controller'
        },
        colorbindingchanged: {
            fn    : 'onColorBindingChanged',
            scope : 'controller'
        },
        huebindingchanged: {
            fn    : 'onHueBindingChanged',
            scope : 'controller'
        }
    },

    afterRender: function () {
        var me = this,
            src = me.mapGradientUrl,
            el = me.el;

        me.callParent();

        if (!src) {
            // We do this trick to allow the Sass to calculate resource image path for
            // our package and pick up the proper image URL here.
            src = el.getStyle('background-image');
            src = src.substring(4, src.length - 1);  // strip off outer "url(...)"

            // Then remember it on our prototype for any subsequent instances.
            Ext.ux.colorpick.ColorMap.prototype.mapGradientUrl = src;
        }

        // Now clear that style because it will conflict with the background-color
        el.setStyle('background-image', 'none');

        // Create the image with transparent PNG with black and white gradient shades;
        // it blends with the background color (which changes with hue selection). This
        // must be an IMG in order to properly stretch to fit.
        el = me.layout.getElementTarget(); // the el for items and html
        el.createChild({
            tag: 'img',
            cls: 'x-colorpicker-colormap-blender',
            src: src
        });
    },

    // Called via data binding whenever selectedColor changes; fires "colorbindingchanged"
    setPosition: function(data) {
        var me         = this,
            dragHandle = me.down('#dragHandle');

        // Too early in the render cycle? Skip event
        if (!dragHandle.dd || !dragHandle.dd.constrain) {
            return;
        }

        // User actively dragging? Skip event
        if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
            return;
        }

        me.fireEvent('colorbindingchanged', data);
    },

    // Called via data binding whenever selectedColor.h changes; fires "huebindingchanged" event
    setHue: function(hue) {
        var me = this;

        // Too early in the render cycle? Skip event
        if (!me.getEl()) {
            return;
        }

        me.fireEvent('huebindingchanged', hue);
    }
});
<commit_msg>IE8 fix for ColorMap background image not showing<commit_after>/**
 * The main colorful square for selecting color shades by dragging around the
 * little circle.
 */
Ext.define('Ext.ux.colorpick.ColorMap', {
    extend     : 'Ext.container.Container',
    alias      : 'widget.colorpickercolormap',
    controller : 'colorpickercolormapcontroller',

    requires: [
        'Ext.ux.colorpick.ColorMapController'
    ],

    cls  : 'x-colorpicker-colormap',

    // This is the drag "circle"; note it's 1x1 in size to allow full
    // travel around the color map; the inner div has the bigger image
    items: [{
        xtype     : 'component',
        cls       : 'x-colorpicker-colormap-draghandle-container',
        itemId    : 'dragHandle',
        width     : 1,
        height    : 1,
        draggable : true,
        html: '

'
    }],

    listeners : {
        boxready : {
            single  : true,
            fn      : 'onFirstBoxReady',
            scope   : 'controller'
        },
        colorbindingchanged: {
            fn    : 'onColorBindingChanged',
            scope : 'controller'
        },
        huebindingchanged: {
            fn    : 'onHueBindingChanged',
            scope : 'controller'
        }
    },

    afterRender: function () {
        var me = this,
            src = me.mapGradientUrl,
            el = me.el;

        me.callParent();

        if (!src) {
            // We do this trick to allow the Sass to calculate resource image path for
            // our package and pick up the proper image URL here.
            src = el.getStyle('background-image');
            src = src.substring(4, src.length - 1);  // strip off outer "url(...)"

            // In IE8 this path will have quotes around it
            if (src.indexOf('"') === 0) {
                src = src.substring(1, src.length-1);
            }

            // Then remember it on our prototype for any subsequent instances.
            Ext.ux.colorpick.ColorMap.prototype.mapGradientUrl = src;
        }

        // Now clear that style because it will conflict with the background-color
        el.setStyle('background-image', 'none');

        // Create the image with transparent PNG with black and white gradient shades;
        // it blends with the background color (which changes with hue selection). This
        // must be an IMG in order to properly stretch to fit.
        el = me.layout.getElementTarget(); // the el for items and html
        el.createChild({
            tag: 'img',
            cls: 'x-colorpicker-colormap-blender',
            src: src
        });
    },

    // Called via data binding whenever selectedColor changes; fires "colorbindingchanged"
    setPosition: function(data) {
        var me         = this,
            dragHandle = me.down('#dragHandle');

        // Too early in the render cycle? Skip event
        if (!dragHandle.dd || !dragHandle.dd.constrain) {
            return;
        }

        // User actively dragging? Skip event
        if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
            return;
        }

        me.fireEvent('colorbindingchanged', data);
    },

    // Called via data binding whenever selectedColor.h changes; fires "huebindingchanged" event
    setHue: function(hue) {
        var me = this;

        // Too early in the render cycle? Skip event
        if (!me.getEl()) {
            return;
        }

        me.fireEvent('huebindingchanged', hue);
    }
});
<|endoftext|>


<commit_before>import { inject as service } from '@ember/service';
import { alias } from '@ember/object/computed';
import Controller, { inject as controller } from '@ember/controller';
import { computed } from '@ember/object';
import Sortable from 'nomad-ui/mixins/sortable';
import Searchable from 'nomad-ui/mixins/searchable';

export default Controller.extend(Sortable, Searchable, {
  system: service(),
  jobsController: controller('jobs'),

  isForbidden: alias('jobsController.isForbidden'),

  queryParams: {
    currentPage: 'page',
    searchTerm: 'search',
    sortProperty: 'sort',
    sortDescending: 'desc',
  },

  currentPage: 1,
  pageSize: 10,

  sortProperty: 'modifyIndex',
  sortDescending: true,

  searchProps: computed(() => ['id', 'name']),
  fuzzySearchProps: computed(() => ['name']),
  fuzzySearchEnabled: true,

  facetOptionsType: computed(() => [
    { key: 'batch', label: 'Batch' },
    { key: 'parameterized', label: 'Parameterized' },
    { key: 'periodic', label: 'Periodic' },
    { key: 'service', label: 'Service' },
    { key: 'system', label: 'System' },
  ]),

  facetOptionsStatus: computed(() => [
    { key: 'pending', label: 'Pending' },
    { key: 'running', label: 'Running' },
    { key: 'dead', label: 'Dead' },
  ]),

  facetOptionsDatacenter: computed('model.[]', function() {
    return [{ key: 'dc1', label: 'dc1' }];
  }),

  facetOptionsPrefix: computed('model.[]', function() {
    return [{ key: 'atlas-', label: 'atlas-' }];
  }),

  facetSelectionType: computed(() => []),
  facetSelectionStatus: computed(() => []),
  facetSelectionDatacenter: computed(() => []),
  facetSelectionPrefix: computed(() => []),

  /**
    Filtered jobs are those that match the selected namespace and aren't children
    of periodic or parameterized jobs.
  */
  filteredJobs: computed('model.[]', 'model.@each.parent', function() {
    // Namespace related properties are ommitted from the dependent keys
    // due to a prop invalidation bug caused by region switching.
    const hasNamespaces = this.get('system.namespaces.length');
    const activeNamespace = this.get('system.activeNamespace.id') || 'default';

    return this.get('model')
      .compact()
      .filter(job => !hasNamespaces || job.get('namespace.id') === activeNamespace)
      .filter(job => !job.get('parent.content'));
  }),

  listToSort: alias('filteredJobs'),
  listToSearch: alias('listSorted'),
  sortedJobs: alias('listSearched'),

  isShowingDeploymentDetails: false,

  actions: {
    gotoJob(job) {
      this.transitionToRoute('jobs.job', job.get('plainId'));
    },
  },
});
<commit_msg>Implement the dynamic facet lists (datacenters and prefixes)<commit_after>import { inject as service } from '@ember/service';
import { alias } from '@ember/object/computed';
import Controller, { inject as controller } from '@ember/controller';
import { computed } from '@ember/object';
import Sortable from 'nomad-ui/mixins/sortable';
import Searchable from 'nomad-ui/mixins/searchable';

export default Controller.extend(Sortable, Searchable, {
  system: service(),
  jobsController: controller('jobs'),

  isForbidden: alias('jobsController.isForbidden'),

  queryParams: {
    currentPage: 'page',
    searchTerm: 'search',
    sortProperty: 'sort',
    sortDescending: 'desc',
  },

  currentPage: 1,
  pageSize: 10,

  sortProperty: 'modifyIndex',
  sortDescending: true,

  searchProps: computed(() => ['id', 'name']),
  fuzzySearchProps: computed(() => ['name']),
  fuzzySearchEnabled: true,

  facetOptionsType: computed(() => [
    { key: 'batch', label: 'Batch' },
    { key: 'parameterized', label: 'Parameterized' },
    { key: 'periodic', label: 'Periodic' },
    { key: 'service', label: 'Service' },
    { key: 'system', label: 'System' },
  ]),

  facetOptionsStatus: computed(() => [
    { key: 'pending', label: 'Pending' },
    { key: 'running', label: 'Running' },
    { key: 'dead', label: 'Dead' },
  ]),

  facetOptionsDatacenter: computed('visibleJobs.[]', function() {
    const flatten = (acc, val) => acc.concat(val);
    const allDatacenters = new Set(
      this.get('visibleJobs')
        .mapBy('datacenters')
        .reduce(flatten, [])
    );

    return Array.from(allDatacenters)
      .compact()
      .sort()
      .map(dc => ({ key: dc, label: dc }));
  }),

  facetOptionsPrefix: computed('visibleJobs.[]', function() {
    // A prefix is defined as the start of a job name up to the first - or .
    // ex: mktg-analytics -> mktg, ds.supermodel.classifier -> ds
    const hasPrefix = /.[-._]/;

    // Collect and count all the prefixes
    const allNames = this.get('visibleJobs').mapBy('name');
    const nameHistogram = allNames.reduce((hist, name) => {
      if (hasPrefix.test(name)) {
        const prefix = name.match(/(.+?)[-.]/)[1];
        hist[prefix] = hist[prefix] ? hist[prefix] + 1 : 1;
      }
      return hist;
    }, {});

    // Convert to an array
    const nameTable = Object.keys(nameHistogram).map(key => ({
      prefix: key,
      count: nameHistogram[key],
    }));

    // Only consider prefixes that match more than one name, then convert to an
    // options array, including the counts in the label
    return nameTable
      .filter(name => name.count > 1)
      .sortBy('prefix')
      .reverse()
      .map(name => ({
        key: name.prefix,
        label: `${name.prefix} (${name.count})`,
      }));
  }),

  facetSelectionType: computed(() => []),
  facetSelectionStatus: computed(() => []),
  facetSelectionDatacenter: computed(() => []),
  facetSelectionPrefix: computed(() => []),

  /**
    Filtered jobs are those that match the selected namespace and aren't children
    of periodic or parameterized jobs.
  */
  visibleJobs: computed('model.[]', 'model.@each.parent', function() {
    // Namespace related properties are ommitted from the dependent keys
    // due to a prop invalidation bug caused by region switching.
    const hasNamespaces = this.get('system.namespaces.length');
    const activeNamespace = this.get('system.activeNamespace.id') || 'default';

    return this.get('model')
      .compact()
      .filter(job => !hasNamespaces || job.get('namespace.id') === activeNamespace)
      .filter(job => !job.get('parent.content'));
  }),

  listToSort: alias('filteredJobs'),
  listToSearch: alias('listSorted'),
  sortedJobs: alias('listSearched'),

  isShowingDeploymentDetails: false,

  actions: {
    gotoJob(job) {
      this.transitionToRoute('jobs.job', job.get('plainId'));
    },
  },
});
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */
var express = require('express')
  , utils = require('../utils')
  , dispatch = require('../middleware/dispatch')
  , entryHelper = require('../helpers/route/entry')
  , patternHelper = require('../helpers/route/pattern');

/**
 * Express underlay.
 *
 * This module sets up an underlying Express app for the Locomotive application.
 *
 * Locomotive is just a collection of higher-level constructs layered on top of
 * Express, bringing structure and conventions used when building RESTful web
 * applications using the MVC pattern.
 *
 * Express is used as the underlay, handling the middleware stack and
 * routing requests.  This allows existing knowledge and the ecosystem of
 * Express middleware and view engines to be reused.
 *
 * @param {String} env
 * @api private
 */
module.exports = function(env) {
  env = env || process.env.NODE_ENV || 'development';
  var self = this
    , app = express();

  // Configure the router.
  //
  // The router needs a handler (which dispatches requests to a controller
  // action), a mechanism to mount those handlers in the underlying Express app,
  // and routing assistance which declares route helpers.
  this.__router.handler(function(controller, action) {
    return dispatch(self, controller, action);
  });
  this.__router.define(function(method, path, handler) {
    app[method](path, handler);
  });
  this.__router.assist(function(name, entry) {
    if (typeof entry == 'string') {
      self.helper(name + 'Path', patternHelper.path(entry));
      self.dynamicHelper(name + 'URL', patternHelper.url(entry, name));
      return;
    }

    var placeholders = [];
    entry.keys.forEach(function(key) {
      if (!key.optional) { placeholders.push(key.name); }
    });
    self.helper(name + 'Path', entryHelper(entry.controller, entry.action, placeholders, true));
    self.helper(name + 'URL', entryHelper(entry.controller, entry.action, placeholders));
  });

  // Forward function calls from Locomotive to Express.  This allows Locomotive
  // to be used interchangably with Express.
  utils.forward(this, app, [ 'configure',
                             'get', 'set',
                             'enabled', 'disabled',
                             'enable', 'disable',
                             'use', 'engine' ]);
  this.express = app;
  this.router = app.router;
  this.locals = app.locals;
  this.mime = express.static.mime;

  // Set the environment.  This syncs Express with the environment supplied to
  // the Locomotive CLI.
  this.set('env', env);
};
<commit_msg>Update express.js<commit_after>/**
 * Module dependencies.
 */
var express = require('express')
  , utils = require('../utils')
  , dispatch = require('../middleware/dispatch')
  , entryHelper = require('../helpers/route/entry')
  , patternHelper = require('../helpers/route/pattern');

/**
 * Express underlay.
 *
 * This module sets up an underlying Express app for the Locomotive application.
 *
 * Locomotive is just a collection of higher-level constructs layered on top of
 * Express, bringing structure and conventions used when building RESTful web
 * applications using the MVC pattern.
 *
 * Express is used as the underlay, handling the middleware stack and
 * routing requests.  This allows existing knowledge and the ecosystem of
 * Express middleware and view engines to be reused.
 *
 * @param {String} env
 * @api private
 */
module.exports = function(env) {
  env = env || process.env.NODE_ENV || 'development';
  var self = this
    , app = express();

  // Configure the router.
  //
  // The router needs a handler (which dispatches requests to a controller
  // action), a mechanism to mount those handlers in the underlying Express app,
  // and routing assistance which declares route helpers.
  this.__router.handler(function(controller, action) {
    return dispatch(self, controller, action);
  });
  this.__router.define(function(method, path, handler) {
    app[method](path, handler);
  });
  this.__router.assist(function(name, entry) {
    if (typeof entry == 'string') {
      self.helper(name + 'Path', patternHelper.path(entry));
      self.dynamicHelper(name + 'URL', patternHelper.url(entry, name));
      return;
    }

    var placeholders = [];
    entry.keys.forEach(function(key) {
      if (!key.optional) { placeholders.push(key.name); }
    });
    self.helper(name + 'Path', entryHelper(entry.controller, entry.action, placeholders, true));
    self.helper(name + 'URL', entryHelper(entry.controller, entry.action, placeholders));
  });

  // Forward function calls from Locomotive to Express.  This allows Locomotive
  // to be used interchangably with Express.
  utils.forward(this, app, [ 'get', 'set',
                             'enabled', 'disabled',
                             'enable', 'disable',
                             'use', 'engine' ]);
  this.express = app;
  this.router = app.router;
  this.locals = app.locals;
  this.mime = express.static.mime;

  // Set the environment.  This syncs Express with the environment supplied to
  // the Locomotive CLI.
  this.set('env', env);
};
<|endoftext|>


<commit_before>/*global window, define*/
// Copyright (c) 2016 Sune Simonsen <sune@we-knowhow.dk>
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation

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

(function(root, factory) {
  if (typeof exports === 'object') {
    module.exports = factory();
  } else if (typeof define === 'function' && define.amd) {
    define(factory);
  } else {
    root.weknowhow = root.weknowhow || {};
    root.weknowhow.unexpectedCheck = factory();
  }
})(this, function() {
  var defaultMaxIterations = 300;
  if (
    typeof process !== 'undefined' &&
    process.env.UNEXPECTED_CHECK_MAX_ITERATIONS
  ) {
    defaultMaxIterations = parseInt(
      process.env.UNEXPECTED_CHECK_MAX_ITERATIONS,
      10
    );
  } else if (
    typeof window !== 'undefined' &&
    typeof window.location !== 'undefined'
  ) {
    var m = window.location.search.match(/[?&]maxiterations=(\d+)(?:$|&)/);
    if (m) {
      defaultMaxIterations = parseInt(m[1], 10);
    }
  }

  function copyProximity() {
    var proximity = global.recordProximity.proximity;
    var aKeys = Object.keys(proximity)
      .map(Number)
      .sort(function(a, b) {
        return a - b;
      });

    return aKeys.reduce(function(result, key) {
      result[key] = proximity[key];
      return result;
    }, {});
  }

  function isProximityLessThan(a, b) {
    // keys are lready ordered
    var aKeys = Object.keys(a);
    var bKeys = Object.keys(b);

    for (var i = 0; i < aKeys.length; i += 1) {
      var aKey = Number(aKeys[i]);
      var bKey = Number(bKeys[i]);

      if (aKey < bKey) {
        return true;
      } else if (aKey > bKey) {
        return false;
      }

      var aCount = a[aKey];
      var bCount = b[bKey] || 0;
      if (aCount > bCount) {
        return true;
      } else if (aCount < bCount) {
        return false;
      }
    }

    return aKeys.length > bKeys.length;
  }

  return {
    name: 'unexpected-check',
    installInto: function(expect) {
      expect.addType({
        base: 'object',
        name: 'chance-generator',
        identify: function(value) {
          return value && value.isGenerator;
        },
        inspect: function(value, depth, output) {
          output.jsFunctionName(value.generatorName);
          if (value.options && Object.keys(value.options).length > 0) {
            output
              .text('(')
              .appendInspected(value.options)
              .text(')');
          }
        }
      });

      expect.addType({
        base: 'chance-generator',
        name: 'mapped-chance-generator',
        identify: function(value) {
          return value && value.isMappedGenerator;
        },
        inspect: function(value, depth, output, inspect) {
          output
            .appendInspected(value.parentGenerator)
            .text('.')
            .jsFunctionName('map')
            .text('(')
            .appendInspected(value.options.mapper)
            .text(')');
        }
      });

      expect.addType({
        name: 'fuzzed-generator',
        identify: function(value) {
          return value && value.isFuzzedGenerator;
        },
        inspect: function(value, depth, output, inspect) {
          output
            .jsFunctionName('fuzz')
            .text('(')
            .appendItems(value.args, ', ')
            .text(', ')
            .appendInspected(value.mutatorFunction)
            .text(')');
        }
      });

      var promiseLoop = function(condition, action) {
        return expect.promise(function(resolve, reject) {
          var loop = function() {
            if (!condition()) {
              return resolve();
            }

            return action()
              .then(loop)
              .catch(reject);
          };

          loop();
        });
      };

      expect.addAssertion('<function> to be valid for all <object>', function(
        expect,
        subject,
        options
      ) {
        var generators = options.generators || [];
        var maxIterations = options.maxIterations || defaultMaxIterations;
        var maxErrorIterations = options.maxErrorIterations || 1000;
        var maxErrors = options.maxErrors || 201;
        var interestingInputs = {};
        var hasInstrumentation = !!global.recordLocation;

        var iterators = generators.map(function(generator) {
          if (!generator.values) {
            throw new Error(
              'Generators needs to have a values method that returns an iterator\n' +
                'See: https://sunesimonsen.github.io/chance-generators/api/generator/'
            );
          }

          return generator.values();
        });

        function resetIterators() {
          iterators = generators.map(function(generator) {
            return generator.values();
          });
        }

        function createTask() {
          var args = iterators.map(function(iterator) {
            return iterator.next();
          });

          var task = {
            args: args
          };

          return task;
        }

        function hasShrinkableIterators() {
          return iterators.some(function(iterator) {
            return iterator.isShrinkable;
          });
        }

        function runTasks() {
          var tasks = [];
          var errors = 0;
          var i = 0;
          var j = 0;

          function calculateInvestigations() {
            return Math.min(Math.floor((maxIterations - i) * 0.1), 10000);
          }

          var investigations = Math.min(Math.ceil(maxIterations * 0.01), 1000);

          return promiseLoop(
            function() {
              return (
                (errors === 0
                  ? i++ < maxIterations
                  : j++ < maxErrorIterations) &&
                errors < maxErrors &&
                (errors === 0 || hasShrinkableIterators())
              );
            },
            function() {
              var task = createTask();
              tasks.push(task);

              if (hasInstrumentation) {
                global.recordLocation.reset();
              }

              return expect
                .promise(function() {
                  return subject.apply(null, task.args);
                })
                .then(
                  function() {
                    if (hasInstrumentation && errors === 0) {
                      var key = Object.keys(
                        global.recordLocation.locations
                      ).join(',');
                      var interestingInput = interestingInputs[key];
                      investigations--;

                      if (interestingInput) {
                        if (investigations > 0) {
                          var proximity = copyProximity();
                          if (
                            isProximityLessThan(
                              proximity,
                              interestingInput.proximity
                            )
                          ) {
                            interestingInput.deadEnd = false;
                            interestingInput.input = task.args;
                            interestingInput.proximity = proximity;
                          }
                        }

                        if (interestingInput.deadEnd || investigations === 0) {
                          investigations = calculateInvestigations();

                          // TODO check the iterations
                          interestingInput.deadEnd = true;

                          var keys = Object.keys(interestingInputs);

                          var newInterestingInput = null;
                          for (
                            var i = keys.length - 1;
                            i >= 0 && !newInterestingInput;
                            i--
                          ) {
                            var candidate = interestingInputs[keys[i]];
                            if (!candidate.deadEnd) {
                              newInterestingInput = candidate;
                            }
                          }

                          resetIterators();

                          if (newInterestingInput) {
                            iterators.forEach(function(iterator, i) {
                              iterator.expand(newInterestingInput.input[i]);
                            });
                          }
                        }
                      } else {
                        interestingInputs[key] = {
                          input: task.args,
                          location: Object.create(
                            global.recordLocation.locations
                          ),
                          proximity: copyProximity()
                        };
                      }
                    }
                  },
                  function(err) {
                    if (errors === 0) {
                      resetIterators();
                    }

                    iterators.forEach(function(iterator, i) {
                      iterator.shrink(task.args[i]);
                    });
                    task.error = err;
                    errors++;
                  }
                );
            }
          ).then(function() {
            return {
              tasks: tasks,
              iterations: i,
              errorIterations: j,
              errors: errors
            };
          });
        }

        return runTasks().then(function(execution) {
          var failedTasks = execution.tasks.filter(function(task) {
            return task.error;
          });

          if (failedTasks.length > 0) {
            var bestFailure = failedTasks[failedTasks.length - 1];

            expect.errorMode = 'bubble';
            expect.fail(function(output) {
              output
                .error('Found an error after')
                .sp()
                .jsNumber(execution.iterations)
                .sp()
                .error(execution.iterations > 1 ? 'iterations' : 'iteration');

              if (execution.errors > 1) {
                output
                  .error(',')
                  .sp()
                  .jsNumber(execution.errors - 1)
                  .sp()
                  .error('additional')
                  .sp()
                  .error(execution.errors > 2 ? 'errors' : 'error')
                  .sp()
                  .error('found.');
              }

              output
                .nl()
                .jsComment('counterexample:')
                .nl(2);

              output.indentLines();
              output.i().block(function(output) {
                output
                  .text('Generated input: ')
                  .appendItems(bestFailure.args, ', ')
                  .nl()
                  .text('with: ')
                  .appendItems(options.generators, ', ')
                  .nl(2)
                  .block(function(output) {
                    output.appendErrorMessage(bestFailure.error);
                  });
              });
            });
          }
        });
      });

      expect.addAssertion(
        '<function> to be valid for all <chance-generator+>',
        function(expect, subject) {
          expect.errorMode = 'bubble';

          return expect(subject, 'to be valid for all', {
            generators: Array.prototype.slice.call(arguments, 2)
          });
        }
      );

      function FuzzedGenerator(value, mutatingGenerator) {
        this.generatorName = 'fuzz';
        this.isGenerator = true;
        this.options = {
          value: value,
          mutator: mutatingGenerator
        };
      }

      FuzzedGenerator.prototype.values = function() {
        return this.options.mutator.values();
      };

      expect.addAssertion(
        '<any> [when] fuzzed by <function> <assertion>',
        function(expect, subject, mutator) {
          expect.errorMode = 'bubble';
          const mutatingGenerator = mutator(subject);

          expect(mutatingGenerator, 'to satisfy', {
            isGenerator: true
          });

          return expect(
            function(value) {
              return expect.shift(value);
            },
            'to be valid for all',
            new FuzzedGenerator(subject, mutatingGenerator)
          );
        }
      );
    }
  };
});
<commit_msg>Fix typo in comment<commit_after>/*global window, define*/
// Copyright (c) 2016 Sune Simonsen <sune@we-knowhow.dk>
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation

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

(function(root, factory) {
  if (typeof exports === 'object') {
    module.exports = factory();
  } else if (typeof define === 'function' && define.amd) {
    define(factory);
  } else {
    root.weknowhow = root.weknowhow || {};
    root.weknowhow.unexpectedCheck = factory();
  }
})(this, function() {
  var defaultMaxIterations = 300;
  if (
    typeof process !== 'undefined' &&
    process.env.UNEXPECTED_CHECK_MAX_ITERATIONS
  ) {
    defaultMaxIterations = parseInt(
      process.env.UNEXPECTED_CHECK_MAX_ITERATIONS,
      10
    );
  } else if (
    typeof window !== 'undefined' &&
    typeof window.location !== 'undefined'
  ) {
    var m = window.location.search.match(/[?&]maxiterations=(\d+)(?:$|&)/);
    if (m) {
      defaultMaxIterations = parseInt(m[1], 10);
    }
  }

  function copyProximity() {
    var proximity = global.recordProximity.proximity;
    var aKeys = Object.keys(proximity)
      .map(Number)
      .sort(function(a, b) {
        return a - b;
      });

    return aKeys.reduce(function(result, key) {
      result[key] = proximity[key];
      return result;
    }, {});
  }

  function isProximityLessThan(a, b) {
    // keys are already ordered
    var aKeys = Object.keys(a);
    var bKeys = Object.keys(b);

    for (var i = 0; i < aKeys.length; i += 1) {
      var aKey = Number(aKeys[i]);
      var bKey = Number(bKeys[i]);

      if (aKey < bKey) {
        return true;
      } else if (aKey > bKey) {
        return false;
      }

      var aCount = a[aKey];
      var bCount = b[bKey] || 0;
      if (aCount > bCount) {
        return true;
      } else if (aCount < bCount) {
        return false;
      }
    }

    return aKeys.length > bKeys.length;
  }

  return {
    name: 'unexpected-check',
    installInto: function(expect) {
      expect.addType({
        base: 'object',
        name: 'chance-generator',
        identify: function(value) {
          return value && value.isGenerator;
        },
        inspect: function(value, depth, output) {
          output.jsFunctionName(value.generatorName);
          if (value.options && Object.keys(value.options).length > 0) {
            output
              .text('(')
              .appendInspected(value.options)
              .text(')');
          }
        }
      });

      expect.addType({
        base: 'chance-generator',
        name: 'mapped-chance-generator',
        identify: function(value) {
          return value && value.isMappedGenerator;
        },
        inspect: function(value, depth, output, inspect) {
          output
            .appendInspected(value.parentGenerator)
            .text('.')
            .jsFunctionName('map')
            .text('(')
            .appendInspected(value.options.mapper)
            .text(')');
        }
      });

      expect.addType({
        name: 'fuzzed-generator',
        identify: function(value) {
          return value && value.isFuzzedGenerator;
        },
        inspect: function(value, depth, output, inspect) {
          output
            .jsFunctionName('fuzz')
            .text('(')
            .appendItems(value.args, ', ')
            .text(', ')
            .appendInspected(value.mutatorFunction)
            .text(')');
        }
      });

      var promiseLoop = function(condition, action) {
        return expect.promise(function(resolve, reject) {
          var loop = function() {
            if (!condition()) {
              return resolve();
            }

            return action()
              .then(loop)
              .catch(reject);
          };

          loop();
        });
      };

      expect.addAssertion('<function> to be valid for all <object>', function(
        expect,
        subject,
        options
      ) {
        var generators = options.generators || [];
        var maxIterations = options.maxIterations || defaultMaxIterations;
        var maxErrorIterations = options.maxErrorIterations || 1000;
        var maxErrors = options.maxErrors || 201;
        var interestingInputs = {};
        var hasInstrumentation = !!global.recordLocation;

        var iterators = generators.map(function(generator) {
          if (!generator.values) {
            throw new Error(
              'Generators needs to have a values method that returns an iterator\n' +
                'See: https://sunesimonsen.github.io/chance-generators/api/generator/'
            );
          }

          return generator.values();
        });

        function resetIterators() {
          iterators = generators.map(function(generator) {
            return generator.values();
          });
        }

        function createTask() {
          var args = iterators.map(function(iterator) {
            return iterator.next();
          });

          var task = {
            args: args
          };

          return task;
        }

        function hasShrinkableIterators() {
          return iterators.some(function(iterator) {
            return iterator.isShrinkable;
          });
        }

        function runTasks() {
          var tasks = [];
          var errors = 0;
          var i = 0;
          var j = 0;

          function calculateInvestigations() {
            return Math.min(Math.floor((maxIterations - i) * 0.1), 10000);
          }

          var investigations = Math.min(Math.ceil(maxIterations * 0.01), 1000);

          return promiseLoop(
            function() {
              return (
                (errors === 0
                  ? i++ < maxIterations
                  : j++ < maxErrorIterations) &&
                errors < maxErrors &&
                (errors === 0 || hasShrinkableIterators())
              );
            },
            function() {
              var task = createTask();
              tasks.push(task);

              if (hasInstrumentation) {
                global.recordLocation.reset();
              }

              return expect
                .promise(function() {
                  return subject.apply(null, task.args);
                })
                .then(
                  function() {
                    if (hasInstrumentation && errors === 0) {
                      var key = Object.keys(
                        global.recordLocation.locations
                      ).join(',');
                      var interestingInput = interestingInputs[key];
                      investigations--;

                      if (interestingInput) {
                        if (investigations > 0) {
                          var proximity = copyProximity();
                          if (
                            isProximityLessThan(
                              proximity,
                              interestingInput.proximity
                            )
                          ) {
                            interestingInput.deadEnd = false;
                            interestingInput.input = task.args;
                            interestingInput.proximity = proximity;
                          }
                        }

                        if (interestingInput.deadEnd || investigations === 0) {
                          investigations = calculateInvestigations();

                          // TODO check the iterations
                          interestingInput.deadEnd = true;

                          var keys = Object.keys(interestingInputs);

                          var newInterestingInput = null;
                          for (
                            var i = keys.length - 1;
                            i >= 0 && !newInterestingInput;
                            i--
                          ) {
                            var candidate = interestingInputs[keys[i]];
                            if (!candidate.deadEnd) {
                              newInterestingInput = candidate;
                            }
                          }

                          resetIterators();

                          if (newInterestingInput) {
                            iterators.forEach(function(iterator, i) {
                              iterator.expand(newInterestingInput.input[i]);
                            });
                          }
                        }
                      } else {
                        interestingInputs[key] = {
                          input: task.args,
                          location: Object.create(
                            global.recordLocation.locations
                          ),
                          proximity: copyProximity()
                        };
                      }
                    }
                  },
                  function(err) {
                    if (errors === 0) {
                      resetIterators();
                    }

                    iterators.forEach(function(iterator, i) {
                      iterator.shrink(task.args[i]);
                    });
                    task.error = err;
                    errors++;
                  }
                );
            }
          ).then(function() {
            return {
              tasks: tasks,
              iterations: i,
              errorIterations: j,
              errors: errors
            };
          });
        }

        return runTasks().then(function(execution) {
          var failedTasks = execution.tasks.filter(function(task) {
            return task.error;
          });

          if (failedTasks.length > 0) {
            var bestFailure = failedTasks[failedTasks.length - 1];

            expect.errorMode = 'bubble';
            expect.fail(function(output) {
              output
                .error('Found an error after')
                .sp()
                .jsNumber(execution.iterations)
                .sp()
                .error(execution.iterations > 1 ? 'iterations' : 'iteration');

              if (execution.errors > 1) {
                output
                  .error(',')
                  .sp()
                  .jsNumber(execution.errors - 1)
                  .sp()
                  .error('additional')
                  .sp()
                  .error(execution.errors > 2 ? 'errors' : 'error')
                  .sp()
                  .error('found.');
              }

              output
                .nl()
                .jsComment('counterexample:')
                .nl(2);

              output.indentLines();
              output.i().block(function(output) {
                output
                  .text('Generated input: ')
                  .appendItems(bestFailure.args, ', ')
                  .nl()
                  .text('with: ')
                  .appendItems(options.generators, ', ')
                  .nl(2)
                  .block(function(output) {
                    output.appendErrorMessage(bestFailure.error);
                  });
              });
            });
          }
        });
      });

      expect.addAssertion(
        '<function> to be valid for all <chance-generator+>',
        function(expect, subject) {
          expect.errorMode = 'bubble';

          return expect(subject, 'to be valid for all', {
            generators: Array.prototype.slice.call(arguments, 2)
          });
        }
      );

      function FuzzedGenerator(value, mutatingGenerator) {
        this.generatorName = 'fuzz';
        this.isGenerator = true;
        this.options = {
          value: value,
          mutator: mutatingGenerator
        };
      }

      FuzzedGenerator.prototype.values = function() {
        return this.options.mutator.values();
      };

      expect.addAssertion(
        '<any> [when] fuzzed by <function> <assertion>',
        function(expect, subject, mutator) {
          expect.errorMode = 'bubble';
          const mutatingGenerator = mutator(subject);

          expect(mutatingGenerator, 'to satisfy', {
            isGenerator: true
          });

          return expect(
            function(value) {
              return expect.shift(value);
            },
            'to be valid for all',
            new FuzzedGenerator(subject, mutatingGenerator)
          );
        }
      );
    }
  };
});
<|endoftext|>


<commit_before>'use strict';
import React, {
  AppRegistry,
  ActivityIndicatorIOS,
  ListView,
  Component,
  StyleSheet,
  Text,
  Image,
  TouchableHighlight,
  RefreshControl,
  View
} from 'react-native';

var Helpers = require('./helpers.js');

var TAKE_ACTION_TEXT = 'Take action';

var NewsFeedView = React.createClass({
  getInitialState: function() {
    return {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      isRefreshing: false,
      loaded: false,
    };
  },
  componentDidMount: function() {
    this.fetchData();
  },
  fetchData: function() {
    fetch(this.props.url)
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          dataSource: this.state.dataSource.cloneWithRows(responseData.posts),
          loaded: true,
        });
      })
      .done();
  },
  render: function() {
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderPost}
        style={styles.listView}
        refreshControl={
          <RefreshControl
            refreshing={this.state.isRefreshing}
            onRefresh={this._onRefresh}
            tintColor="#3932A9"
            colors={['#ff0000', '#00ff00', '#0000ff']}
            progressBackgroundColor="#ffff00"
          />
        }
      />
    );
  },
  _onRefresh: function () {
    this.setState({isRefreshing: true});
    setTimeout(() => {
      this.fetchData();
      this.setState({
        isRefreshing: false,
      });
    }, 1000);
  },
  renderLoadingView: function() {
    return (
      <View style={styles.loadingContainer}>
        <ActivityIndicatorIOS animating={this.state.animating} style={[{height: 80}]} size="small" />
        <Text style={styles.subtitle}>
          Loading news...
        </Text>
      </View>
    );
  },
  renderPost: function(post) {
    var imgBackground;
    var imgOval;

    if (typeof post !== 'undefined'
        && typeof post.attachments[0] !== 'undefined'
        && typeof post.attachments[0].images !== 'undefined'
        && typeof post.attachments[0].images.full !== 'undefined') {
        imgBackground = <Image
          style={{flex: 1, height: 128, alignItems: 'stretch'}}
          source={{uri: post.attachments[0].images.full.url}}>
            <View style={styles.titleContainer}>
              <Text style={styles.title}>{post.title.toUpperCase()}</Text>
            </View>
          </Image>;
    }
    else {
      imgBackground = <Text style={styles.title}>{post.title.toUpperCase()}</Text>;
    }

    imgOval = require('image!listitem-oval');

    var linkToArticle;
    if (typeof post.custom_fields.full_article_url !== 'undefined'
        && typeof post.custom_fields.full_article_url[0] !== 'undefined'
        && post.custom_fields.full_article_url[0]) {
        linkToArticle = <Text
            onPress={this.fullArticlePressed.bind(this, post.custom_fields.full_article_url[0])}
            style={styles.articleLink}>
            Read the full article
          </Text>;
    }
    else {
      linkToArticle = null;
    }

    var formattedDate = Helpers.formatDate(post.date);
    var viewCategory = null;
    if (post.categories.length > 0) {
      viewCategory =
        <View style={styles.categoryContainer}>
          <Text style={styles.category}>{post.categories[0].title}</Text>
        </View>;
    }

    return(
      <View style={styles.postContainer}>
        <View style={styles.postHeader}>
          <Text style={styles.date}>{formattedDate}</Text>
          {viewCategory}
        </View>
        {imgBackground}
        <View style={styles.postBody}>
          <Text style={styles.subtitle}>{post.custom_fields.subtitle}</Text>
          <View style={styles.summaryItem}>
            <View style={styles.listItemOvalContainer}>
              <Image source={imgOval} />
            </View>
            <Text style={styles.summaryText}>{post.custom_fields.summary_1}</Text>
          </View>
          <View style={styles.summaryItem}>
            <View style={styles.listItemOvalContainer}>
              <Image source={imgOval} />
            </View>
            <Text style={styles.summaryText}>{post.custom_fields.summary_2}</Text>
          </View>
          <View style={styles.summaryItem}>
            <View style={styles.listItemOvalContainer}>
              <Image source={imgOval} />
            </View>
            <Text style={styles.summaryText}>{post.custom_fields.summary_3}</Text>
          </View>
          {linkToArticle}
        </View>
        <TouchableHighlight onPress={this.ctaButtonPressed.bind(this, post)} style={styles.btn}>
          <Text style={styles.btnText}>{TAKE_ACTION_TEXT.toUpperCase()}</Text>
        </TouchableHighlight>
      </View>
    );
  },
  ctaButtonPressed: function(post) {
    var campaignID = post.custom_fields.campaign_id[0];
    var NewsFeedViewController = require('react-native').NativeModules.LDTNewsFeedViewController;
    NewsFeedViewController.presentCampaignWithCampaignID(campaignID);
  },
  fullArticlePressed: function(url) {
    var NewsFeedViewController = require('react-native').NativeModules.LDTNewsFeedViewController;
    NewsFeedViewController.presentFullArticleWithUrlString(url);
  },
});

var styles = React.StyleSheet.create({
  postBody: {
    padding: 10,
  },
  postContainer: {
    backgroundColor: '#ffffff',
    marginTop: 10
  },
  loadingContainer: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#EEE',
  },
  postHeader: {
    flex: 1,
    flexDirection: 'row',
    backgroundColor: '#00e4c8',
    borderTopLeftRadius: 4,
    borderTopRightRadius: 4,
    padding: 4,
  },
  postHeaderText: {
    color: '#ffffff',
  },
  articleLink: {
    color: '#3932A9',
    fontFamily: 'BrandonGrotesque-Bold',
  },
  btn: {
    backgroundColor: '#3932A9',
    borderBottomLeftRadius: 4,
    borderBottomRightRadius: 4,
    paddingBottom: 10,
    paddingTop: 10,
  },
  btnText: {
    color: '#ffffff',
    fontFamily: 'BrandonGrotesque-Bold',
    fontSize: 16,
    textAlign: 'center',
  },
  category: {
    color: '#ffffff',
    fontFamily: 'Brandon Grotesque',
  },
  categoryContainer: {
    flex: 1,
    alignItems: 'flex-end',
  },
  date: {
    color: '#ffffff',
    fontFamily: 'Brandon Grotesque',
  },
  // View container to center the image against just a single line of text
  listItemOvalContainer: {
    // This height is based off the draw height of a single summaryText line
    height: 21.5,
    justifyContent: 'center',
  },
  listView: {
    backgroundColor: '#eeeeee',
    paddingLeft: 10,
    paddingRight: 10,
    paddingBottom: 10,
  },
  subtitle: {
    color: '#4A4A4A',
    fontFamily: 'BrandonGrotesque-Bold',
    fontSize: 18,
  },
  summaryItem: {
    flex: 1,
    flexDirection: 'row',
    marginBottom: 8,
    marginTop: 8,
  },
  summaryText: {
    color: '#4A4A4A',
    flex: 1,
    flexDirection: 'column',
    fontFamily: 'Brandon Grotesque',
    fontSize: 15,
    marginLeft: 4,
  },
  title: {
    color: '#ffffff',
    flex: 1,
    flexDirection: 'column',
    fontFamily: 'BrandonGrotesque-Bold',
    fontSize: 20,
    textAlign: 'center',
  },
  titleContainer: {
    backgroundColor: 'rgba(0,0,0,0.3)',
    alignItems: 'center',
    flex: 1,
    flexDirection: 'row',
    padding: 20,
  },
});
AppRegistry.registerComponent('NewsFeedView', () => NewsFeedView);
<commit_msg>Set postHeader color to dummy color if Cause exists<commit_after>'use strict';
import React, {
  AppRegistry,
  ActivityIndicatorIOS,
  ListView,
  Component,
  StyleSheet,
  Text,
  Image,
  TouchableHighlight,
  RefreshControl,
  View
} from 'react-native';

var Helpers = require('./helpers.js');

var TAKE_ACTION_TEXT = 'Take action';

var NewsFeedView = React.createClass({
  getInitialState: function() {
    return {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      isRefreshing: false,
      loaded: false,
    };
  },
  componentDidMount: function() {
    this.fetchData();
  },
  fetchData: function() {
    fetch(this.props.url)
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          dataSource: this.state.dataSource.cloneWithRows(responseData.posts),
          loaded: true,
        });
      })
      .done();
  },
  render: function() {
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderPost}
        style={styles.listView}
        refreshControl={
          <RefreshControl
            refreshing={this.state.isRefreshing}
            onRefresh={this._onRefresh}
            tintColor="#3932A9"
            colors={['#ff0000', '#00ff00', '#0000ff']}
            progressBackgroundColor="#ffff00"
          />
        }
      />
    );
  },
  _onRefresh: function () {
    this.setState({isRefreshing: true});
    setTimeout(() => {
      this.fetchData();
      this.setState({
        isRefreshing: false,
      });
    }, 1000);
  },
  renderLoadingView: function() {
    return (
      <View style={styles.loadingContainer}>
        <ActivityIndicatorIOS animating={this.state.animating} style={[{height: 80}]} size="small" />
        <Text style={styles.subtitle}>
          Loading news...
        </Text>
      </View>
    );
  },
  renderPost: function(post) {
    var imgBackground;
    var imgOval;

    if (typeof post !== 'undefined'
        && typeof post.attachments[0] !== 'undefined'
        && typeof post.attachments[0].images !== 'undefined'
        && typeof post.attachments[0].images.full !== 'undefined') {
        imgBackground = <Image
          style={{flex: 1, height: 128, alignItems: 'stretch'}}
          source={{uri: post.attachments[0].images.full.url}}>
            <View style={styles.titleContainer}>
              <Text style={styles.title}>{post.title.toUpperCase()}</Text>
            </View>
          </Image>;
    }
    else {
      imgBackground = <Text style={styles.title}>{post.title.toUpperCase()}</Text>;
    }

    imgOval = require('image!listitem-oval');

    var linkToArticle;
    if (typeof post.custom_fields.full_article_url !== 'undefined'
        && typeof post.custom_fields.full_article_url[0] !== 'undefined'
        && post.custom_fields.full_article_url[0]) {
        linkToArticle = <Text
            onPress={this.fullArticlePressed.bind(this, post.custom_fields.full_article_url[0])}
            style={styles.articleLink}>
            Read the full article
          </Text>;
    }
    else {
      linkToArticle = null;
    }

    var formattedDate = Helpers.formatDate(post.date);
    var causeStyle = null;
    var causeTitle = null;
    if (post.categories.length > 0) {
      causeTitle = post.categories[0].title;
      causeStyle = {backgroundColor: '#FF0033'};
    }

    return(
      <View style={styles.postContainer}>
        <View style={[styles.postHeader, causeStyle]}>
          <Text style={styles.date}>{formattedDate}</Text>
          <View style={styles.categoryContainer}>
            <Text style={styles.category}>{causeTitle}</Text>
          </View>
        </View>
        {imgBackground}
        <View style={styles.postBody}>
          <Text style={styles.subtitle}>{post.custom_fields.subtitle}</Text>
          <View style={styles.summaryItem}>
            <View style={styles.listItemOvalContainer}>
              <Image source={imgOval} />
            </View>
            <Text style={styles.summaryText}>{post.custom_fields.summary_1}</Text>
          </View>
          <View style={styles.summaryItem}>
            <View style={styles.listItemOvalContainer}>
              <Image source={imgOval} />
            </View>
            <Text style={styles.summaryText}>{post.custom_fields.summary_2}</Text>
          </View>
          <View style={styles.summaryItem}>
            <View style={styles.listItemOvalContainer}>
              <Image source={imgOval} />
            </View>
            <Text style={styles.summaryText}>{post.custom_fields.summary_3}</Text>
          </View>
          {linkToArticle}
        </View>
        <TouchableHighlight onPress={this.ctaButtonPressed.bind(this, post)} style={styles.btn}>
          <Text style={styles.btnText}>{TAKE_ACTION_TEXT.toUpperCase()}</Text>
        </TouchableHighlight>
      </View>
    );
  },
  ctaButtonPressed: function(post) {
    var campaignID = post.custom_fields.campaign_id[0];
    var NewsFeedViewController = require('react-native').NativeModules.LDTNewsFeedViewController;
    NewsFeedViewController.presentCampaignWithCampaignID(campaignID);
  },
  fullArticlePressed: function(url) {
    var NewsFeedViewController = require('react-native').NativeModules.LDTNewsFeedViewController;
    NewsFeedViewController.presentFullArticleWithUrlString(url);
  },
});

var styles = React.StyleSheet.create({
  postBody: {
    padding: 10,
  },
  postContainer: {
    backgroundColor: '#ffffff',
    marginTop: 10
  },
  loadingContainer: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#EEE',
  },
  postHeader: {
    flex: 1,
    flexDirection: 'row',
    backgroundColor: '#00e4c8',
    borderTopLeftRadius: 4,
    borderTopRightRadius: 4,
    padding: 4,
  },
  postHeaderText: {
    color: '#ffffff',
  },
  articleLink: {
    color: '#3932A9',
    fontFamily: 'BrandonGrotesque-Bold',
  },
  btn: {
    backgroundColor: '#3932A9',
    borderBottomLeftRadius: 4,
    borderBottomRightRadius: 4,
    paddingBottom: 10,
    paddingTop: 10,
  },
  btnText: {
    color: '#ffffff',
    fontFamily: 'BrandonGrotesque-Bold',
    fontSize: 16,
    textAlign: 'center',
  },
  category: {
    color: '#ffffff',
    fontFamily: 'Brandon Grotesque',
  },
  categoryContainer: {
    flex: 1,
    alignItems: 'flex-end',
  },
  date: {
    color: '#ffffff',
    fontFamily: 'Brandon Grotesque',
  },
  // View container to center the image against just a single line of text
  listItemOvalContainer: {
    // This height is based off the draw height of a single summaryText line
    height: 21.5,
    justifyContent: 'center',
  },
  listView: {
    backgroundColor: '#eeeeee',
    paddingLeft: 10,
    paddingRight: 10,
    paddingBottom: 10,
  },
  subtitle: {
    color: '#4A4A4A',
    fontFamily: 'BrandonGrotesque-Bold',
    fontSize: 18,
  },
  summaryItem: {
    flex: 1,
    flexDirection: 'row',
    marginBottom: 8,
    marginTop: 8,
  },
  summaryText: {
    color: '#4A4A4A',
    flex: 1,
    flexDirection: 'column',
    fontFamily: 'Brandon Grotesque',
    fontSize: 15,
    marginLeft: 4,
  },
  title: {
    color: '#ffffff',
    flex: 1,
    flexDirection: 'column',
    fontFamily: 'BrandonGrotesque-Bold',
    fontSize: 20,
    textAlign: 'center',
  },
  titleContainer: {
    backgroundColor: 'rgba(0,0,0,0.3)',
    alignItems: 'center',
    flex: 1,
    flexDirection: 'row',
    padding: 20,
  },
});
AppRegistry.registerComponent('NewsFeedView', () => NewsFeedView);
<|endoftext|>


<commit_before>'use strict';

[{
  zoneRegex: {
    en: /^Middle La Noscea$/,
    cn: /^中拉诺西亚$/,
    ko: /^중부 라노시아$/,
  },
  timelineFile: 'test.txt',
  // timeline here is additions to the timeline.  They can
  // be strings, or arrays of strings, or functions that
  // take the same data object (including role and lang)
  // that triggers do.
  timeline: [
    'alerttext "Final Sting" before 4 "oh no final sting in 4"',
    'alarmtext "Death" before 3',
    'alertall "Long Castbar" before 1 speak "voice" "long"',
    function(data) {
      if (data.role != 'tank' && data.role != 'healer')
        return 'hideall "Super Tankbuster"';
      return 'alarmtext "Super Tankbuster" before 2';
    },
    function(data) {
      if (!data.role.startsWith('dps'))
        return 'hideall "Pentacle Sac (DPS)"';
    },
    function(data) {
      if (data.role != 'healer')
        return 'hideall "Almagest"';
      return 'alarmtext "Almagest" before 0';
    },
    function(data) {
      // <_<
      let shortName = data.me.indexOf(' ') >= 0 ? data.me.substring(0, data.me.indexOf(' ')) : data.me;
      return [
        '40 "Death To ' + shortName + '!!"',
        'hideall "Death"',
      ];
    },
  ],
  timelineStyles: [
    {
      regex: /^Death To/,
      style: {
        'color': 'red',
        'font-family': 'Impact',
      },
    },
  ],
  timelineTriggers: [
    {
      id: 'Test Angry Dummy',
      regex: /(Angry Dummy)/,
      beforeSeconds: 2,
      infoText: function(data, matches) {
        return {
          en: 'Stack for ' + matches[1],
          de: 'Sammeln für ' + matches[1],
          cn: '木人处集合',
          ko: matches[1] + '에 집합',
        };
      },
      tts: {
        en: 'Stack',
        de: 'Sammeln',
        cn: '集合',
        ko: '집합',
      },
    },
    {
      id: 'Test Delayed Dummy',
      regex: /(Angry Dummy)/,
      // Add in a huge delay to make it obvious the delay runs before promise.
      delaySeconds: 10,
      promise: function(data, matches) {
        data.delayedDummyTimestampBefore = Date.now();
        let p = new Promise((res) => {
          window.setTimeout(() => {
            data.delayedDummyTimestampAfter = Date.now();
            res();
          }, 3000);
        });
        return p;
      },
      infoText: function(data) {
        let elapsed = data.delayedDummyTimestampAfter - data.delayedDummyTimestampBefore;
        return {
          en: 'Elapsed ms: ' + elapsed,
          de: 'Abgelaufene ms: ' + elapsed,
        };
      },
    },
  ],
  triggers: [
    {
      id: 'Test Poke',
      netRegex: NetRegexes.gameNameLog({ line: 'You poke the striking dummy.*?', capture: false }),
      netRegexDe: NetRegexes.gameNameLog({ line: 'Du stupst die Trainingspuppe an.*?', capture: false }),
      netRegexFr: NetRegexes.gameNameLog({ line: 'Vous touchez légèrement le mannequin d\'entraînement du doigt.*?', capture: false }),
      netRegexCn: NetRegexes.gameNameLog({ line: '.*用手指戳向木人.*?', capture: false }),
      netRegexKo: NetRegexes.gameNameLog({ line: '.*나무인형을 쿡쿡 찌릅니다.*?', capture: false }),
      preRun: function(data) {
        data.pokes = (data.pokes || 0) + 1;
      },
      infoText: function(data) {
        return {
          en: 'poke #' + data.pokes,
          de: 'stups #' + data.pokes,
          fr: 'Touché #' + data.pokes,
          cn: '戳 #' + data.pokes,
          ko: data.pokes + '번 찌름',
        };
      },
    },
    {
      id: 'Test Psych',
      regex: Regexes.gameNameLog({ line: 'You psych yourself up alongside the striking dummy.*.*?', capture: false }),
      regexDe: Regexes.gameNameLog({ line: 'Du willst wahren Kampfgeist in der Trainingspuppe entfachen.*?', capture: false }),
      regexFr: Regexes.gameNameLog({ line: 'Vous vous motivez devant le mannequin d\'entraînement.*?', capture: false }),
      regexCn: Regexes.gameNameLog({ line: '.*激励木人.*?', capture: false }),
      regexKo: Regexes.gameNameLog({ line: '.*나무인형에게 힘을 불어넣습니다.*?', capture: false }),
      alertText: {
        en: 'PSYCH!!!',
        de: 'AUF GEHTS!!!',
        fr: 'MOTIVATION !!!',
        cn: '激励！！',
        ko: '힘내라!!',
      },
      groupTTS: {
        en: 'group psych',
        de: 'Gruppen auf gehts',
        fr: 'group motivation',
        cn: '组激励',
        ko: '단체 격려',
      },
      tts: {
        en: 'psych',
        de: 'auf gehts',
        fr: 'Motivation',
        cn: '激励',
        ko: '힘내라!',
      },
    },
    {
      id: 'Test Laugh',
      regex: Regexes.gameNameLog({ line: 'You burst out laughing at the striking dummy.*?', capture: false }),
      regexDe: Regexes.gameNameLog({ line: 'Du lachst herzlich mit der Trainingspuppe.*?', capture: false }),
      regexFr: Regexes.gameNameLog({ line: 'Vous vous esclaffez devant le mannequin d\'entraînement.*?', capture: false }),
      regexCn: Regexes.gameNameLog({ line: '.*看着木人高声大笑.*?', capture: false }),
      regexKo: Regexes.gameNameLog({ line: '.*나무인형을 보고 폭소를 터뜨립니다.*?', capture: false }),
      suppressSeconds: 5,
      alarmText: {
        en: 'hahahahaha',
        de: 'hahahahaha',
        fr: 'Mouahahaha',
        cn: '2333333333',
        ko: '푸하하하하핳',
      },
      groupTTS: {
        en: 'group laugh',
        de: 'Gruppenlache',
        fr: 'group motivation',
        cn: '组哈哈',
        ko: '단체 웃음',
      },
      tts: {
        en: 'hahahahaha',
        de: 'hahahahaha',
        fr: 'Haha mort de rire',
        cn: '哈哈哈哈哈哈',
        ko: '푸하하하하핳',
      },
    },
    {
      id: 'Test Clap',
      regex: Regexes.gameNameLog({ line: 'You clap for the striking dummy.*?', capture: false }),
      regexDe: Regexes.gameNameLog({ line: 'Du klatschst begeistert Beifall für die Trainingspuppe.*?', capture: false }),
      regexFr: Regexes.gameNameLog({ line: 'Vous applaudissez le mannequin d\'entraînement.*?', capture: false }),
      regexCn: Regexes.gameNameLog({ line: '.*向木人送上掌声.*?', capture: false }),
      regexKo: Regexes.gameNameLog({ line: '.*나무인형에게 박수를 보냅니다.*?', capture: false }),
      sound: '../../resources/sounds/WeakAuras/Applause.ogg',
      soundVolume: 0.3,
      tts: {
        en: 'clapity clap',
        de: 'klatschen',
        fr: 'Bravo, vive la France',
        cn: '鼓掌',
        ko: '박수 짝짝짝',
      },
    },
    {
      id: 'Test Lang',
      // In game: /echo cactbot lang
      regex: Regexes.echo({ line: 'cactbot lang.*?', capture: false }),
      regexDe: Regexes.echo({ line: 'cactbot sprache.*?', capture: false }),
      regexKo: Regexes.echo({ line: 'cactbot 언어.*?', capture: false }),
      infoText: function(data) {
        return {
          en: 'Language: ' + data.parserLang,
          de: 'Sprache: ' + data.parserLang,
          cn: '语言: ' + data.parserLang,
          ko: '언어: ' + data.parserLang,
        };
      },
    },
    {
      id: 'Test Response',
      regex: Regexes.echo({ line: 'cactbot test response.*?', capture: false }),
      regexDe: Regexes.echo({ line: 'cactbot test antwort.*?', capture: false }),
      response: function(data) {
        return {
          alarmText: '1',
          alertText: '2',
          infoText: '3',
          tts: '4',
        };
      },
    },
  ],
  timelineReplace: [
    {
      locale: 'de',
      replaceSync: {
        'You bid farewell to the striking dummy': 'Du winkst der Trainingspuppe zum Abschied zu',
        'You bow courteously to the striking dummy': 'Du verbeugst dich hochachtungsvoll vor der Trainingspuppe',
        'test sync': 'test sync',
        'You burst out laughing at the striking dummy': 'Du lachst herzlich mit der Trainingspuppe',
        'cactbot lang': 'cactbot sprache',
        'cactbot test response': 'cactbot test antwort',
        'You clap for the striking dummy': 'Du klatschst begeistert Beifall für die Trainingspuppe',
        'You psych yourself up alongside the striking dummy.*': 'Du willst wahren Kampfgeist in der Trainingspuppe entfachen',
        'You poke the striking dummy': 'Du stupst die Trainingspuppe an',
      },
      replaceText: {
        'Final Sting': 'Schlussstich',
        'Almagest': 'Almagest',
        'Angry Dummy': 'Wütender Dummy',
        'Long Castbar': 'Langer Zauberbalken',
        'Dummy Stands Still': 'Dummy still stehen',
        'Death': 'Tot',
        'Super Tankbuster': 'Super Tankbuster',
        'Pentacle Sac': 'Pentacle Sac',
        'Engage': 'Start!',
      },
    },
    {
      locale: 'fr',
      missingTranslations: true,
      replaceSync: {
        'You bid farewell to the striking dummy': 'Vous faites vos adieux au mannequin d\'entraînement',
        'You bow courteously to the striking dummy': 'Vous vous inclinez devant le mannequin d\'entraînement',
      },
      replaceText: {
        'Final Sting': 'Dard final',
        'Almagest': 'Almageste',
        'Angry Dummy': 'Mannequin en colère',
        'Long Castbar': 'Longue barre de lancement',
        'Dummy Stands Still': 'Mannequin immobile',
        'Death': 'Mort',
      },
    },
    {
      locale: 'cn',
      missingTranslations: true,
      replaceSync: {
        'You bid farewell to the striking dummy': '.*向木人告别',
        'You bow courteously to the striking dummy': '.*恭敬地对木人行礼',
      },
      replaceText: {
        'Final Sting': '终极针',
        'Almagest': '至高无上',
        'Angry Dummy': '愤怒的木人',
        'Long Castbar': '长时间咏唱',
        'Dummy Stands Still': '木人8动了',
        'Super Tankbuster': '超级无敌转圈死刑',
        'Death To': '嗝屁攻击：',
        'Death': '嗝屁',
        'Engage': '战斗开始',
      },
    },
    {
      locale: 'ko',
      replaceSync: {
        'You bid farewell to the striking dummy': '.*나무인형에게 작별 인사를 합니다',
        'You bow courteously to the striking dummy': '.*나무인형에게 공손하게 인사합니다',
        'test sync': '테스트 싱크',
      },
      replaceText: {
        'Final Sting': '마지막 벌침',
        'Almagest': '알마게스트',
        'Angry Dummy': '화난 나무인형',
        'Long Castbar': '긴 시전바',
        'Dummy Stands Still': '나무인형이 아직 살아있다',
        'Death': '데스',
        'Super Tankbuster': '초강력 탱크버스터',
        'Pentacle Sac': 'Pentacle Sac',
        'Engage': '시작',
      },
    },
  ],
}];
<commit_msg>raidboss: add fr translation to test<commit_after>'use strict';

[{
  zoneRegex: {
    en: /^Middle La Noscea$/,
    cn: /^中拉诺西亚$/,
    ko: /^중부 라노시아$/,
  },
  timelineFile: 'test.txt',
  // timeline here is additions to the timeline.  They can
  // be strings, or arrays of strings, or functions that
  // take the same data object (including role and lang)
  // that triggers do.
  timeline: [
    'alerttext "Final Sting" before 4 "oh no final sting in 4"',
    'alarmtext "Death" before 3',
    'alertall "Long Castbar" before 1 speak "voice" "long"',
    function(data) {
      if (data.role != 'tank' && data.role != 'healer')
        return 'hideall "Super Tankbuster"';
      return 'alarmtext "Super Tankbuster" before 2';
    },
    function(data) {
      if (!data.role.startsWith('dps'))
        return 'hideall "Pentacle Sac (DPS)"';
    },
    function(data) {
      if (data.role != 'healer')
        return 'hideall "Almagest"';
      return 'alarmtext "Almagest" before 0';
    },
    function(data) {
      // <_<
      let shortName = data.me.indexOf(' ') >= 0 ? data.me.substring(0, data.me.indexOf(' ')) : data.me;
      return [
        '40 "Death To ' + shortName + '!!"',
        'hideall "Death"',
      ];
    },
  ],
  timelineStyles: [
    {
      regex: /^Death To/,
      style: {
        'color': 'red',
        'font-family': 'Impact',
      },
    },
  ],
  timelineTriggers: [
    {
      id: 'Test Angry Dummy',
      regex: /(Angry Dummy)/,
      beforeSeconds: 2,
      infoText: function(data, matches) {
        return {
          en: 'Stack for ' + matches[1],
          de: 'Sammeln für ' + matches[1],
          cn: '木人处集合',
          ko: matches[1] + '에 집합',
        };
      },
      tts: {
        en: 'Stack',
        de: 'Sammeln',
        cn: '集合',
        ko: '집합',
      },
    },
    {
      id: 'Test Delayed Dummy',
      regex: /(Angry Dummy)/,
      // Add in a huge delay to make it obvious the delay runs before promise.
      delaySeconds: 10,
      promise: function(data, matches) {
        data.delayedDummyTimestampBefore = Date.now();
        let p = new Promise((res) => {
          window.setTimeout(() => {
            data.delayedDummyTimestampAfter = Date.now();
            res();
          }, 3000);
        });
        return p;
      },
      infoText: function(data) {
        let elapsed = data.delayedDummyTimestampAfter - data.delayedDummyTimestampBefore;
        return {
          en: 'Elapsed ms: ' + elapsed,
          de: 'Abgelaufene ms: ' + elapsed,
        };
      },
    },
  ],
  triggers: [
    {
      id: 'Test Poke',
      netRegex: NetRegexes.gameNameLog({ line: 'You poke the striking dummy.*?', capture: false }),
      netRegexDe: NetRegexes.gameNameLog({ line: 'Du stupst die Trainingspuppe an.*?', capture: false }),
      netRegexFr: NetRegexes.gameNameLog({ line: 'Vous touchez légèrement le mannequin d\'entraînement du doigt.*?', capture: false }),
      netRegexCn: NetRegexes.gameNameLog({ line: '.*用手指戳向木人.*?', capture: false }),
      netRegexKo: NetRegexes.gameNameLog({ line: '.*나무인형을 쿡쿡 찌릅니다.*?', capture: false }),
      preRun: function(data) {
        data.pokes = (data.pokes || 0) + 1;
      },
      infoText: function(data) {
        return {
          en: 'poke #' + data.pokes,
          de: 'stups #' + data.pokes,
          fr: 'Touché #' + data.pokes,
          cn: '戳 #' + data.pokes,
          ko: data.pokes + '번 찌름',
        };
      },
    },
    {
      id: 'Test Psych',
      regex: Regexes.gameNameLog({ line: 'You psych yourself up alongside the striking dummy.*.*?', capture: false }),
      regexDe: Regexes.gameNameLog({ line: 'Du willst wahren Kampfgeist in der Trainingspuppe entfachen.*?', capture: false }),
      regexFr: Regexes.gameNameLog({ line: 'Vous vous motivez devant le mannequin d\'entraînement.*?', capture: false }),
      regexCn: Regexes.gameNameLog({ line: '.*激励木人.*?', capture: false }),
      regexKo: Regexes.gameNameLog({ line: '.*나무인형에게 힘을 불어넣습니다.*?', capture: false }),
      alertText: {
        en: 'PSYCH!!!',
        de: 'AUF GEHTS!!!',
        fr: 'MOTIVATION !!!',
        cn: '激励！！',
        ko: '힘내라!!',
      },
      groupTTS: {
        en: 'group psych',
        de: 'Gruppen auf gehts',
        fr: 'group motivation',
        cn: '组激励',
        ko: '단체 격려',
      },
      tts: {
        en: 'psych',
        de: 'auf gehts',
        fr: 'Motivation',
        cn: '激励',
        ko: '힘내라!',
      },
    },
    {
      id: 'Test Laugh',
      regex: Regexes.gameNameLog({ line: 'You burst out laughing at the striking dummy.*?', capture: false }),
      regexDe: Regexes.gameNameLog({ line: 'Du lachst herzlich mit der Trainingspuppe.*?', capture: false }),
      regexFr: Regexes.gameNameLog({ line: 'Vous vous esclaffez devant le mannequin d\'entraînement.*?', capture: false }),
      regexCn: Regexes.gameNameLog({ line: '.*看着木人高声大笑.*?', capture: false }),
      regexKo: Regexes.gameNameLog({ line: '.*나무인형을 보고 폭소를 터뜨립니다.*?', capture: false }),
      suppressSeconds: 5,
      alarmText: {
        en: 'hahahahaha',
        de: 'hahahahaha',
        fr: 'Mouahahaha',
        cn: '2333333333',
        ko: '푸하하하하핳',
      },
      groupTTS: {
        en: 'group laugh',
        de: 'Gruppenlache',
        fr: 'group motivation',
        cn: '组哈哈',
        ko: '단체 웃음',
      },
      tts: {
        en: 'hahahahaha',
        de: 'hahahahaha',
        fr: 'Haha mort de rire',
        cn: '哈哈哈哈哈哈',
        ko: '푸하하하하핳',
      },
    },
    {
      id: 'Test Clap',
      regex: Regexes.gameNameLog({ line: 'You clap for the striking dummy.*?', capture: false }),
      regexDe: Regexes.gameNameLog({ line: 'Du klatschst begeistert Beifall für die Trainingspuppe.*?', capture: false }),
      regexFr: Regexes.gameNameLog({ line: 'Vous applaudissez le mannequin d\'entraînement.*?', capture: false }),
      regexCn: Regexes.gameNameLog({ line: '.*向木人送上掌声.*?', capture: false }),
      regexKo: Regexes.gameNameLog({ line: '.*나무인형에게 박수를 보냅니다.*?', capture: false }),
      sound: '../../resources/sounds/WeakAuras/Applause.ogg',
      soundVolume: 0.3,
      tts: {
        en: 'clapity clap',
        de: 'klatschen',
        fr: 'Bravo, vive la France',
        cn: '鼓掌',
        ko: '박수 짝짝짝',
      },
    },
    {
      id: 'Test Lang',
      // In game: /echo cactbot lang
      regex: Regexes.echo({ line: 'cactbot lang.*?', capture: false }),
      regexDe: Regexes.echo({ line: 'cactbot sprache.*?', capture: false }),
      regexKo: Regexes.echo({ line: 'cactbot 언어.*?', capture: false }),
      infoText: function(data) {
        return {
          en: 'Language: ' + data.parserLang,
          de: 'Sprache: ' + data.parserLang,
          fr: 'Langage: ' + data.paserLang,
          cn: '语言: ' + data.parserLang,
          ko: '언어: ' + data.parserLang,
        };
      },
    },
    {
      id: 'Test Response',
      regex: Regexes.echo({ line: 'cactbot test response.*?', capture: false }),
      regexDe: Regexes.echo({ line: 'cactbot test antwort.*?', capture: false }),
      response: function(data) {
        return {
          alarmText: '1',
          alertText: '2',
          infoText: '3',
          tts: '4',
        };
      },
    },
  ],
  timelineReplace: [
    {
      locale: 'de',
      replaceSync: {
        'You bid farewell to the striking dummy': 'Du winkst der Trainingspuppe zum Abschied zu',
        'You bow courteously to the striking dummy': 'Du verbeugst dich hochachtungsvoll vor der Trainingspuppe',
        'test sync': 'test sync',
        'You burst out laughing at the striking dummy': 'Du lachst herzlich mit der Trainingspuppe',
        'cactbot lang': 'cactbot sprache',
        'cactbot test response': 'cactbot test antwort',
        'You clap for the striking dummy': 'Du klatschst begeistert Beifall für die Trainingspuppe',
        'You psych yourself up alongside the striking dummy.*': 'Du willst wahren Kampfgeist in der Trainingspuppe entfachen',
        'You poke the striking dummy': 'Du stupst die Trainingspuppe an',
      },
      replaceText: {
        'Final Sting': 'Schlussstich',
        'Almagest': 'Almagest',
        'Angry Dummy': 'Wütender Dummy',
        'Long Castbar': 'Langer Zauberbalken',
        'Dummy Stands Still': 'Dummy still stehen',
        'Death': 'Tot',
        'Super Tankbuster': 'Super Tankbuster',
        'Pentacle Sac': 'Pentacle Sac',
        'Engage': 'Start!',
      },
    },
    {
      locale: 'fr',
      missingTranslations: true,
      replaceSync: {
        'You bid farewell to the striking dummy': 'Vous faites vos adieux au mannequin d\'entraînement',
        'You bow courteously to the striking dummy': 'Vous vous inclinez devant le mannequin d\'entraînement',
      },
      replaceText: {
        'Final Sting': 'Dard final',
        'Almagest': 'Almageste',
        'Angry Dummy': 'Mannequin en colère',
        'Long Castbar': 'Longue barre de lancement',
        'Dummy Stands Still': 'Mannequin immobile',
        'Death': 'Mort',
      },
    },
    {
      locale: 'cn',
      missingTranslations: true,
      replaceSync: {
        'You bid farewell to the striking dummy': '.*向木人告别',
        'You bow courteously to the striking dummy': '.*恭敬地对木人行礼',
      },
      replaceText: {
        'Final Sting': '终极针',
        'Almagest': '至高无上',
        'Angry Dummy': '愤怒的木人',
        'Long Castbar': '长时间咏唱',
        'Dummy Stands Still': '木人8动了',
        'Super Tankbuster': '超级无敌转圈死刑',
        'Death To': '嗝屁攻击：',
        'Death': '嗝屁',
        'Engage': '战斗开始',
      },
    },
    {
      locale: 'ko',
      replaceSync: {
        'You bid farewell to the striking dummy': '.*나무인형에게 작별 인사를 합니다',
        'You bow courteously to the striking dummy': '.*나무인형에게 공손하게 인사합니다',
        'test sync': '테스트 싱크',
      },
      replaceText: {
        'Final Sting': '마지막 벌침',
        'Almagest': '알마게스트',
        'Angry Dummy': '화난 나무인형',
        'Long Castbar': '긴 시전바',
        'Dummy Stands Still': '나무인형이 아직 살아있다',
        'Death': '데스',
        'Super Tankbuster': '초강력 탱크버스터',
        'Pentacle Sac': 'Pentacle Sac',
        'Engage': '시작',
      },
    },
  ],
}];
<|endoftext|>


<commit_before>/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';
import React, {
  AppRegistry,
  ListView,
  Component,
  StyleSheet,
  Text,
  Image,
  TouchableHighlight,
  View
} from 'react-native';

var REQUEST_URL = 'http://dev-ltd-news.pantheon.io/?json=1';
var TAKE_ACTION_TEXT = 'Take action';

var NewsStoryBox = React.createClass({
  getInitialState: function() {
    return {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  },
  componentDidMount: function() {
    this.fetchData();
  },
  fetchData: function() {
    fetch(REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          dataSource: this.state.dataSource.cloneWithRows(responseData.posts),
          loaded: true,
        });
      })
      .done();
  },
  render: function() {
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderNewsStory}
        style={styles.listView}
      />
    );
  },
  renderLoadingView: function() {
    return (
      <View style={styles.container}>
        <Text style={styles.year}>>
          Loading news...
        </Text>
      </View>
    );
  },
  renderNewsStory: function(newsStory) {
    return (
      <View style={styles.container}>
        <Image
          source={{uri: newsStory.attachments[0].url}}
          style={styles.thumbnail}
        />
        <View style={styles.rightContainer}>
          <Text style={styles.title}>{newsStory.title.toUpperCase()}</Text>
          <Text style={styles.year}>{newsStory.custom_fields.subtitle}</Text>
          <TouchableHighlight onPress={this._onPressButton}>
            <Text style={styles.title}>{TAKE_ACTION_TEXT.toUpperCase()}</Text>
          </TouchableHighlight>
        </View>
      </View>
    );
  },
});

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  thumbnail: {
    width: 53,
    height: 81,
  },
  rightContainer: {
    flex: 1,
  },
  title: {
    fontSize: 20,
    marginBottom: 8,
    fontFamily: 'BrandonGrotesque-Bold',
  },
  year: {
    fontFamily: 'Brandon Grotesque',
  },
  listView: {
    paddingTop: 20,
    backgroundColor: '#F5FCFF',
  },
});

AppRegistry.registerComponent('NewsStoryBox', () => NewsStoryBox);
<commit_msg>Adds skeleton ctaButtonPressed function<commit_after>/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';
import React, {
  AppRegistry,
  ListView,
  Component,
  StyleSheet,
  Text,
  Image,
  TouchableHighlight,
  View
} from 'react-native';

var REQUEST_URL = 'http://dev-ltd-news.pantheon.io/?json=1';
var TAKE_ACTION_TEXT = 'Take action';

var NewsStoryBox = React.createClass({
  getInitialState: function() {
    return {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  },
  componentDidMount: function() {
    this.fetchData();
  },
  fetchData: function() {
    fetch(REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          dataSource: this.state.dataSource.cloneWithRows(responseData.posts),
          loaded: true,
        });
      })
      .done();
  },
  render: function() {
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderNewsStory}
        style={styles.listView}
      />
    );
  },
  renderLoadingView: function() {
    return (
      <View style={styles.container}>
        <Text style={styles.year}>>
          Loading news...
        </Text>
      </View>
    );
  },
  renderNewsStory: function(newsStory) {
    return (
      <View style={styles.container}>
        <Image
          source={{uri: newsStory.attachments[0].url}}
          style={styles.thumbnail}
        />
        <View style={styles.rightContainer}>
          <Text style={styles.title}>{newsStory.title.toUpperCase()}</Text>
          <Text style={styles.year}>{newsStory.custom_fields.subtitle}</Text>
          <TouchableHighlight onPress={this.ctaButtonPressed.bind(this)} style={styles.button} underlayColor='#3731A9'>
            <Text style={styles.buttonText}>{TAKE_ACTION_TEXT.toUpperCase()}</Text>
          </TouchableHighlight>
        </View>
      </View>
    );
  },
  ctaButtonPressed: function() {
    console.log('Hello mate');
  },
});

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  thumbnail: {
    width: 53,
    height: 81,
  },
  rightContainer: {
    flex: 1,
  },
  title: {
    fontSize: 20,
    marginBottom: 8,
    fontFamily: 'BrandonGrotesque-Bold',
  },
  year: {
    fontFamily: 'Brandon Grotesque',
  },
  listView: {
    paddingTop: 20,
    backgroundColor: '#F5FCFF',
  },
  buttonText: {
    fontSize: 18,
    color: 'white',
    alignSelf: 'center',
    fontFamily: 'BrandonGrotesque-Bold',
  },
  button: {
    height: 50,
    flex: 1,
    flexDirection: 'row',
    backgroundColor: '#3731A9',
    borderColor: '#FFF',
    marginBottom: 10,
    alignSelf: 'stretch',
    justifyContent: 'center'
  },
});

AppRegistry.registerComponent('NewsStoryBox', () => NewsStoryBox);
<|endoftext|>


<commit_before>import {from, trim, charat, strlen, substr, append, assign} from './Utility.js'

export var line = 1
export var column = 1
export var length = 0
export var position = 0
export var character = 0
export var characters = ''

/**
 * @param {string} value
 * @param {object | null} root
 * @param {object | null} parent
 * @param {string} type
 * @param {string[] | string} props
 * @param {object[] | string} children
 * @param {number} length
 */
export function node (value, root, parent, type, props, children, length) {
	return {value: value, root: root, parent: parent, type: type, props: props, children: children, line: line, column: column, length: length, return: ''}
}

/**
 * @param {object} root
 * @param {object} props
 * @return {object}
 */
export function copy (root, props) {
	return assign({}, root, {length: -root.length}, props)
}

/**
 * @return {number}
 */
export function char () {
	return character
}

/**
 * @return {number}
 */
export function prev () {
	character = position > 0 ? charat(characters, --position) : 0

	if (column--, character === 10)
		column = 1, line--

	return character
}

/**
 * @return {number}
 */
export function next () {
	character = position < length ? charat(characters, position++) : 0

	if (column++, character === 10)
		column = 1, line++

	return character
}

/**
 * @return {number}
 */
export function peek () {
	return charat(characters, position)
}

/**
 * @return {number}
 */
export function caret () {
	return position
}

/**
 * @param {number} begin
 * @param {number} end
 * @return {string}
 */
export function slice (begin, end) {
	return substr(characters, begin, end)
}

/**
 * @param {number} type
 * @return {number}
 */
export function token (type) {
	switch (type) {
		// \0 \t \n \r \s whitespace token
		case 0: case 9: case 10: case 13: case 32:
			return 5
		// ! + , / > @ ~ isolate token
		case 33: case 43: case 44: case 47: case 62: case 64: case 126:
		// ; { } breakpoint token
		case 59: case 123: case 125:
			return 4
		// : accompanied token
		case 58:
			return 3
		// " ' ( [ opening delimit token
		case 34: case 39: case 40: case 91:
			return 2
		// ) ] closing delimit token
		case 41: case 93:
			return 1
	}

	return 0
}

/**
 * @param {string} value
 * @return {any[]}
 */
export function alloc (value) {
	return line = column = 1, length = strlen(characters = value), position = 0, []
}

/**
 * @param {any} value
 * @return {any}
 */
export function dealloc (value) {
	return characters = '', value
}

/**
 * @param {number} type
 * @return {string}
 */
export function delimit (type) {
	return trim(slice(position - 1, delimiter(type === 91 ? type + 2 : type === 40 ? type + 1 : type)))
}

/**
 * @param {string} value
 * @return {string[]}
 */
export function tokenize (value) {
	return dealloc(tokenizer(alloc(value)))
}

/**
 * @param {number} type
 * @return {string}
 */
export function whitespace (type) {
	while (character = peek())
		if (character < 33)
			next()
		else
			break

	return token(type) > 2 || token(character) > 3 ? '' : ' '
}

/**
 * @param {string[]} children
 * @return {string[]}
 */
export function tokenizer (children) {
	while (next())
		switch (token(character)) {
			case 0: append(identifier(position - 1), children)
				break
			case 2: append(delimit(character), children)
				break
			default: append(from(character), children)
		}

	return children
}

/**
 * @param {number} index
 * @param {number} count
 * @return {string}
 */
export function escaping (index, count) {
	while (--count && next())
		// not 0-9 A-F a-f
		if (character < 48 || character > 102 || (character > 57 && character < 65) || (character > 70 && character < 97))
			break

	return slice(index, caret() + (count < 6 && peek() == 32 && next() == 32))
}

/**
 * @param {number} type
 * @return {number}
 */
export function delimiter (type) {
	while (next())
		switch (character) {
			// ] ) " '
			case type:
				return position
			// " '
			case 34: case 39:
				if (type !== 34 && type !== 39)
					delimiter(character)
				break
			// (
			case 40:
				if (type === 41)
					delimiter(type)
				break
			// \
			case 92:
				next()
				break
		}

	return position
}

/**
 * @param {number} type
 * @param {number} index
 * @return {number}
 */
export function commenter (type, index) {
	while (next())
		// //
		if (type + character === 47 + 10)
			break
		// /*
		else if (type + character === 42 + 42 && peek() === 47)
			break

	return '/*' + slice(index, position - 1) + '*' + from(type === 47 ? type : next())
}

/**
 * @param {number} index
 * @return {string}
 */
export function identifier (index) {
	while (!token(peek()))
		next()

	return slice(index, position)
}
<commit_msg>optimization: preserve hidden class when copying objects in `copy` function<commit_after>import {from, trim, charat, strlen, substr, append, assign} from './Utility.js'

export var line = 1
export var column = 1
export var length = 0
export var position = 0
export var character = 0
export var characters = ''

/**
 * @param {string} value
 * @param {object | null} root
 * @param {object | null} parent
 * @param {string} type
 * @param {string[] | string} props
 * @param {object[] | string} children
 * @param {number} length
 */
export function node (value, root, parent, type, props, children, length) {
	return {value: value, root: root, parent: parent, type: type, props: props, children: children, line: line, column: column, length: length, return: ''}
}

/**
 * @param {object} root
 * @param {object} props
 * @return {object}
 */
export function copy (root, props) {
	return assign(node('', null, null, '', null, null, 0), root, {length: -root.length}, props)
}

/**
 * @return {number}
 */
export function char () {
	return character
}

/**
 * @return {number}
 */
export function prev () {
	character = position > 0 ? charat(characters, --position) : 0

	if (column--, character === 10)
		column = 1, line--

	return character
}

/**
 * @return {number}
 */
export function next () {
	character = position < length ? charat(characters, position++) : 0

	if (column++, character === 10)
		column = 1, line++

	return character
}

/**
 * @return {number}
 */
export function peek () {
	return charat(characters, position)
}

/**
 * @return {number}
 */
export function caret () {
	return position
}

/**
 * @param {number} begin
 * @param {number} end
 * @return {string}
 */
export function slice (begin, end) {
	return substr(characters, begin, end)
}

/**
 * @param {number} type
 * @return {number}
 */
export function token (type) {
	switch (type) {
		// \0 \t \n \r \s whitespace token
		case 0: case 9: case 10: case 13: case 32:
			return 5
		// ! + , / > @ ~ isolate token
		case 33: case 43: case 44: case 47: case 62: case 64: case 126:
		// ; { } breakpoint token
		case 59: case 123: case 125:
			return 4
		// : accompanied token
		case 58:
			return 3
		// " ' ( [ opening delimit token
		case 34: case 39: case 40: case 91:
			return 2
		// ) ] closing delimit token
		case 41: case 93:
			return 1
	}

	return 0
}

/**
 * @param {string} value
 * @return {any[]}
 */
export function alloc (value) {
	return line = column = 1, length = strlen(characters = value), position = 0, []
}

/**
 * @param {any} value
 * @return {any}
 */
export function dealloc (value) {
	return characters = '', value
}

/**
 * @param {number} type
 * @return {string}
 */
export function delimit (type) {
	return trim(slice(position - 1, delimiter(type === 91 ? type + 2 : type === 40 ? type + 1 : type)))
}

/**
 * @param {string} value
 * @return {string[]}
 */
export function tokenize (value) {
	return dealloc(tokenizer(alloc(value)))
}

/**
 * @param {number} type
 * @return {string}
 */
export function whitespace (type) {
	while (character = peek())
		if (character < 33)
			next()
		else
			break

	return token(type) > 2 || token(character) > 3 ? '' : ' '
}

/**
 * @param {string[]} children
 * @return {string[]}
 */
export function tokenizer (children) {
	while (next())
		switch (token(character)) {
			case 0: append(identifier(position - 1), children)
				break
			case 2: append(delimit(character), children)
				break
			default: append(from(character), children)
		}

	return children
}

/**
 * @param {number} index
 * @param {number} count
 * @return {string}
 */
export function escaping (index, count) {
	while (--count && next())
		// not 0-9 A-F a-f
		if (character < 48 || character > 102 || (character > 57 && character < 65) || (character > 70 && character < 97))
			break

	return slice(index, caret() + (count < 6 && peek() == 32 && next() == 32))
}

/**
 * @param {number} type
 * @return {number}
 */
export function delimiter (type) {
	while (next())
		switch (character) {
			// ] ) " '
			case type:
				return position
			// " '
			case 34: case 39:
				if (type !== 34 && type !== 39)
					delimiter(character)
				break
			// (
			case 40:
				if (type === 41)
					delimiter(type)
				break
			// \
			case 92:
				next()
				break
		}

	return position
}

/**
 * @param {number} type
 * @param {number} index
 * @return {number}
 */
export function commenter (type, index) {
	while (next())
		// //
		if (type + character === 47 + 10)
			break
		// /*
		else if (type + character === 42 + 42 && peek() === 47)
			break

	return '/*' + slice(index, position - 1) + '*' + from(type === 47 ? type : next())
}

/**
 * @param {number} index
 * @return {string}
 */
export function identifier (index) {
	while (!token(peek()))
		next()

	return slice(index, position)
}
<|endoftext|>