<commit_before>/**
 * ownCloud - Music app
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later. See the COPYING file.
 *
 * @author Pauli Järvinen <pauli.jarvinen@gmail.com>
 * @copyright Pauli Järvinen 2020
 */

angular.module('Music').controller('ArtistDetailsController', [
	'$rootScope', '$scope', 'Restangular', 'gettextCatalog', 'libraryService',
	function ($rootScope, $scope, Restangular, gettextCatalog, libraryService) {

		function resetContents() {
			$scope.artist = null;
			$scope.artistAlbumTrackCount = 0;
			$scope.artistTrackCount = 0;
			$scope.loading = true;
			$scope.artAvailable = false;
			$scope.lastfmInfo = null;
			$scope.artistBio = null;
			$scope.artistTags = null;
			$scope.similarArtists = null;
		}
		resetContents();

		function showDetails(artistId) {
			if (!$scope.artist || artistId != $scope.artist.id) {
				resetContents();

				$scope.artist = libraryService.getArtist(artistId);
				$scope.artistAlbumTrackCount = _.chain($scope.artist.albums).pluck('tracks').flatten().value().length;
				$scope.artistTrackCount = libraryService.findTracksByArtist(artistId).length;

				var art = $('#app-sidebar .albumart');
				art.css('background-image', '');

				Restangular.one('artist', artistId).one('cover').get().then(
					function(result) {
						$scope.artAvailable = true;
						$scope.loading = false;

						var url = OC.generateUrl('apps/music/api/artist/') + artistId + '/cover?originalSize=true';
						art.css('background-image', 'url("' + url + '")');
					},
					function(result) {
						// error handling
						$scope.artAvailable = false;
						$scope.loading = false;
						$scope.noImageHint = gettextCatalog.getString(
							'Upload image named "{{name}}" to anywhere within your library path to see it here.',
							{ name: $scope.artist.name + '.*' }
						);
					}
				);

				Restangular.one('artist', artistId).one('details').get().then(
					function(result) {
						$scope.lastfmInfo = result;
						$scope.artistBio = result.artist.bio.content || result.artist.bio.summary;
						// modify all links in the biography so that they will open to a new tab
						$scope.artistBio = $scope.artistBio.replace(/<a href=/g, '<a target="_blank" href=');

						$scope.artistTags = formatTags(result.artist.tags.tag);
						$scope.similarArtists = formatLinkList(result.artist.similar.artist);

						$scope.$parent.adjustFixedPositions();
					}
				);
			}
		}

		function formatTags(tags) {
			// Filter out the tag "seen live" because it is intended to be used on Last.fm for the feature
			// which allows filtering based on the tags set by the user herself. As a global tag, it makes
			// no sense because almost all artists have been seen live by someone.
			tags = _.reject(tags, {name: 'seen live'});
			return formatLinkList(tags);
		}

		function formatLinkList(linkArray) {
			htmlLinks = _.map(linkArray, function(item) {
				return '' + item.name + '';
			});
			return htmlLinks.join(', ');
		}

		$scope.$watch('contentId', showDetails);
	}
]);
<commit_msg>Fix wrong artist image sometimes being shown<commit_after>/**
 * ownCloud - Music app
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later. See the COPYING file.
 *
 * @author Pauli Järvinen <pauli.jarvinen@gmail.com>
 * @copyright Pauli Järvinen 2020
 */

angular.module('Music').controller('ArtistDetailsController', [
	'$rootScope', '$scope', 'Restangular', 'gettextCatalog', 'libraryService',
	function ($rootScope, $scope, Restangular, gettextCatalog, libraryService) {

		function resetContents() {
			$scope.artist = null;
			$scope.artistAlbumTrackCount = 0;
			$scope.artistTrackCount = 0;
			$scope.loading = true;
			$scope.artAvailable = false;
			$scope.lastfmInfo = null;
			$scope.artistBio = null;
			$scope.artistTags = null;
			$scope.similarArtists = null;
		}
		resetContents();

		function showDetails(artistId) {
			if (!$scope.artist || artistId != $scope.artist.id) {
				resetContents();

				$scope.artist = libraryService.getArtist(artistId);
				$scope.artistAlbumTrackCount = _.chain($scope.artist.albums).pluck('tracks').flatten().value().length;
				$scope.artistTrackCount = libraryService.findTracksByArtist(artistId).length;

				var art = $('#app-sidebar .albumart');
				art.css('background-image', '');

				// Because of the asynchronous nature of teh REST queries, it is possible that the
				// current artist has already changed again by the time we get the result. If that has
				// happened, then the result should be ignored.
				Restangular.one('artist', artistId).one('cover').get().then(
					function(result) {
						if ($scope.artist && $scope.artist.id == artistId) {
							$scope.artAvailable = true;
							$scope.loading = false;

							var url = OC.generateUrl('apps/music/api/artist/') + artistId + '/cover?originalSize=true';
							art.css('background-image', 'url("' + url + '")');
						}
					},
					function(result) {
						// error handling
						if ($scope.artist && $scope.artist.id == artistId) {
							$scope.artAvailable = false;
							$scope.loading = false;
							$scope.noImageHint = gettextCatalog.getString(
								'Upload image named "{{name}}" to anywhere within your library path to see it here.',
								{ name: $scope.artist.name + '.*' }
							);
						}
					}
				);

				Restangular.one('artist', artistId).one('details').get().then(
					function(result) {
						if ($scope.artist && $scope.artist.id == artistId) {
							$scope.lastfmInfo = result;
							$scope.artistBio = result.artist.bio.content || result.artist.bio.summary;
							// modify all links in the biography so that they will open to a new tab
							$scope.artistBio = $scope.artistBio.replace(/<a href=/g, '<a target="_blank" href=');

							$scope.artistTags = formatTags(result.artist.tags.tag);
							$scope.similarArtists = formatLinkList(result.artist.similar.artist);

							$scope.$parent.adjustFixedPositions();
						}
					}
				);
			}
		}

		function formatTags(tags) {
			// Filter out the tag "seen live" because it is intended to be used on Last.fm for the feature
			// which allows filtering based on the tags set by the user herself. As a global tag, it makes
			// no sense because almost all artists have been seen live by someone.
			tags = _.reject(tags, {name: 'seen live'});
			return formatLinkList(tags);
		}

		function formatLinkList(linkArray) {
			htmlLinks = _.map(linkArray, function(item) {
				return '' + item.name + '';
			});
			return htmlLinks.join(', ');
		}

		$scope.$watch('contentId', showDetails);
	}
]);
<|endoftext|>


<commit_before>/* global arango, describe, beforeEach, afterEach, it*/
'use strict';

var expect = require('chai').expect;
var FoxxManager = require('org/arangodb/foxx/manager');
var fs = require('fs');
var internal = require('internal');
var basePath = fs.makeAbsolute(fs.join(internal.startupPath, 'common', 'test-data', 'apps'));
var url = arango.getEndpoint().replace(/\+vpp/, '').replace(/^tcp:/, 'http:').replace(/^ssl:/, 'https:');

describe('HTTP headers in Foxx services', function () {
  describe('Check request-response', function () {
    var mount;

    beforeEach(function () {
      mount = '/unittest/headers';
      try {
        FoxxManager.uninstall(mount, {force: true});
      } catch (err) {
      }
      FoxxManager.install(basePath, mount);
    });

    afterEach(function () {
      FoxxManager.uninstall(mount, {force: true});
    });

    it("sends a CORS options request", function () {
      var opts = { headers: { "origin" : url }, method: "OPTIONS" };
      var result = internal.download(url + "/unittest/headers/header-echo", "", opts);
      expect(result.headers['allow']).to.eql('DELETE, GET, HEAD, PATCH, POST, PUT');
      expect(result.headers['access-control-expose-headers']).to.eql('etag, content-encoding, content-length, location, server, x-arango-errors, x-arango-async-id');
      expect(result.headers['access-control-allow-credentials']).to.eql('true');
      expect(result.headers['access-control-allow-origin']).to.eql(url);
      expect(result.headers['access-control-allow-methods']).to.eql('DELETE, GET, HEAD, PATCH, POST, PUT');
    });

    it("sends a CORS options request, overriding headers", function () {
      var opts = { headers: { "origin" : url, 'x-session-id' : 'abc' } };
      var result = internal.download(url + "/unittest/headers/header-cors", "", opts);
      expect(result.headers['access-control-expose-headers']).to.eql('x-session-id');
    });

    it("echoes back the headers sent", function () {
      var opts = { headers: { "x-test" : "abc", "x-xxxx" : "1234", "X-Testmann" : "1,2,3" } };
      var result = internal.download(url + "/unittest/headers/header-echo", "", opts);

      expect(result.headers['x-xxxx']).to.eql('1234');
      expect(result.headers['x-testmann']).to.eql('1,2,3');
      expect(result.headers['x-test']).to.eql('abc');
    });

    it("returns a static header", function () {
      var result = internal.download(url + "/unittest/headers/header-static");
      expect(result.headers['x-foobar']).to.eql('baz');
    });

  });
});
<commit_msg>fixed test<commit_after>/* global arango, describe, beforeEach, afterEach, it*/
'use strict';

var expect = require('chai').expect;
var FoxxManager = require('org/arangodb/foxx/manager');
var fs = require('fs');
var internal = require('internal');
var basePath = fs.makeAbsolute(fs.join(internal.startupPath, 'common', 'test-data', 'apps', 'headers'));
var url = arango.getEndpoint().replace(/\+vpp/, '').replace(/^tcp:/, 'http:').replace(/^ssl:/, 'https:');

describe('HTTP headers in Foxx services', function () {
  describe('Check request-response', function () {
    var mount;

    beforeEach(function () {
      mount = '/unittest/headers';
      try {
        FoxxManager.uninstall(mount, {force: true});
      } catch (err) {
      }
      FoxxManager.install(basePath, mount);
    });

    afterEach(function () {
      FoxxManager.uninstall(mount, {force: true});
    });

    it("sends a CORS options request", function () {
      var opts = { headers: { "origin" : url }, method: "OPTIONS" };
      var result = internal.download(url + "/unittest/headers/header-echo", "", opts);
      expect(result.code).to.eql(200);
      expect(result.headers['allow']).to.eql('DELETE, GET, HEAD, PATCH, POST, PUT');
      expect(result.headers['access-control-expose-headers']).to.eql('etag, content-encoding, content-length, location, server, x-arango-errors, x-arango-async-id');
      expect(result.headers['access-control-allow-credentials']).to.eql('true');
      expect(result.headers['access-control-allow-origin']).to.eql(url);
      expect(result.headers['access-control-allow-methods']).to.eql('DELETE, GET, HEAD, PATCH, POST, PUT');
    });

    it("sends a CORS options request, overriding headers", function () {
      var opts = { headers: { "origin" : url, 'x-session-id' : 'abc' } };
      var result = internal.download(url + "/unittest/headers/header-cors", "", opts);
      expect(result.code).to.eql(204);
      expect(result.headers['access-control-expose-headers']).to.eql('x-session-id');
    });

    it("echoes back the headers sent", function () {
      var opts = { headers: { "x-test" : "abc", "x-xxxx" : "1234", "X-Testmann" : "1,2,3" } };
      var result = internal.download(url + "/unittest/headers/header-echo", "", opts);

      expect(result.headers['x-xxxx']).to.eql('1234');
      expect(result.headers['x-testmann']).to.eql('1,2,3');
      expect(result.headers['x-test']).to.eql('abc');
    });

    it("returns a static header", function () {
      var result = internal.download(url + "/unittest/headers/header-static");
      expect(result.headers['x-foobar']).to.eql('baz');
    });

  });
});
<|endoftext|>


<commit_before>/*jslint es5: true, indent: 2, nomen: true, maxlen: 120 */
/*global module, require */

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var QUEUE_MANAGER_PERIOD = 1000, // in ms
  _ = require('underscore'),
  tasks = require('org/arangodb/tasks'),
  arangodb = require('org/arangodb'),
  db = arangodb.db,
  queueMap,
  queues,
  Queue,
  worker;

queueMap = Object.create(null);

queues = {
  _jobTypes: Object.create(null),
  get: function (key) {
    'use strict';
    if (!db._queues.exists(key)) {
      throw new Error('Queue does not exist: ' + key);
    }
    if (!queueMap[key]) {
      queueMap[key] = new Queue(key);
    }
    return queueMap[key];
  },
  create: function (key, maxWorkers) {
    'use strict';
    try {
      db._queues.save({_key: key, maxWorkers: maxWorkers});
    } catch (err) {
      if (!err instanceof arangodb.ArangoError ||
          err.errorNum !== arangodb.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
        throw err;
      }
      db._queues.update(key, {maxWorkers: maxWorkers});
    }
    if (!queueMap[key]) {
      queueMap[key] = new Queue(key);
    }
    return queueMap[key];
  },
  destroy: function (key) {
    'use strict';
    var result = false;
    db._executeTransaction({
      collections: {
        read: ['_queues'],
        write: ['_queues']
      },
      action: function () {
        if (db._queues.exists(key)) {
          db._queues.delete(key);
          result = true;
        }
      }
    });
    return result;
  },
  registerJobType: function (type, opts) {
    'use strict';
    if (typeof opts === 'function') {
      opts = {execute: opts};
    }
    if (typeof opts.execute !== 'function') {
      throw new Error('Must provide a function to execute!');
    }
    var cfg = _.extend({maxFailures: 0}, opts);
    if (cfg.maxFailures < 0) {
      cfg.maxFailures = Infinity;
    }
    queues._jobTypes[type] = cfg;
  }
};

Queue = function Queue(name) {
  'use strict';
  Object.defineProperty(this, 'name', {
    get: function () {
      return name;
    },
    set: function (value) {
      throw new Error('name is not mutable');
    },
    configurable: false,
    enumerable: true
  });
};

Queue.prototype.push = function (type, data) {
  'use strict';
  if (typeof type !== 'string') {
    throw new Error('Must pass a job type!');
  }
  db._jobs.save({
    status: 'pending',
    queue: this.name,
    type: type,
    failures: 0,
    data: data
  });
};

queues._worker = {
  work: function (job) {
    'use strict';
    var db = require('org/arangodb').db,
      queues = require('org/arangodb/foxx').queues,
      console = require('console'),
      cfg = queues._jobTypes[job.type],
      failed = false;

    if (!cfg) {
      console.warn('Unknown job type for job ' + job._key + ':', job.type);
      db._jobs.update(job._key, {status: 'pending'});
      return;
    }

    try {
      cfg.execute(job.data);
    } catch (err) {
      console.error('Job ' + job._key + ' failed:', err);
      failed = true;
    }
    db._jobs.update(job._key, failed ? {
      failures: job.failures + 1,
      status: (job.failures + 1 > cfg.maxFailures) ? 'failed' : 'pending'
    } : {status: 'complete'});
  }
};

queues._manager = {
  manage: function () {
    'use strict';
    var _ = require('underscore'),
      db = require('org/arangodb').db,
      queues = require('org/arangodb/foxx').queues,
      tasks = require('org/arangodb/tasks');

    db._executeTransaction({
      collections: {
        read: ['_queues', '_jobs'],
        write: ['_jobs']
      },
      action: function () {
        db._queues.all().toArray().forEach(function (queue) {
          var numBusy = db._jobs.byExample({
            queue: queue._key,
            status: 'progress'
          }).count();
          if (numBusy >= queue.maxWorkers) {
            return;
          }
          db._jobs.byExample({
            queue: queue._key,
            status: 'pending'
          }).limit(queue.maxWorkers - numBusy).toArray().forEach(function (doc) {
            db._jobs.update(doc, {status: 'progress'});
            tasks.register({
              command: queues._worker.work,
              params: _.extend({}, doc, {status: 'progress'}),
              offset: 0
            });
          });
        });
      }
    });
  },
  run: function () {
    'use strict';
    db._jobs.updateByExample({status: 'progress'}, {status: 'pending'});
    tasks.register({command: queues._manager.manage, period: QUEUE_MANAGER_PERIOD / 1000});
  }
};

queues.create('default', 1);

module.exports = queues;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint"
// End:
<commit_msg>Allow creating queues with default number of workers, don't update queue if maxWorkers is not set.<commit_after>/*jslint es5: true, indent: 2, nomen: true, maxlen: 120 */
/*global module, require */

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx queues
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var QUEUE_MANAGER_PERIOD = 1000, // in ms
  _ = require('underscore'),
  tasks = require('org/arangodb/tasks'),
  arangodb = require('org/arangodb'),
  db = arangodb.db,
  queueMap,
  queues,
  Queue,
  worker;

queueMap = Object.create(null);

queues = {
  _jobTypes: Object.create(null),
  get: function (key) {
    'use strict';
    if (!db._queues.exists(key)) {
      throw new Error('Queue does not exist: ' + key);
    }
    if (!queueMap[key]) {
      queueMap[key] = new Queue(key);
    }
    return queueMap[key];
  },
  create: function (key, maxWorkers) {
    'use strict';
    try {
      db._queues.save({_key: key, maxWorkers: maxWorkers || 1});
    } catch (err) {
      if (!err instanceof arangodb.ArangoError ||
          err.errorNum !== arangodb.ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED) {
        throw err;
      }
      if (maxWorkers) {
        db._queues.update(key, {maxWorkers: maxWorkers});
      }
    }
    if (!queueMap[key]) {
      queueMap[key] = new Queue(key);
    }
    return queueMap[key];
  },
  destroy: function (key) {
    'use strict';
    var result = false;
    db._executeTransaction({
      collections: {
        read: ['_queues'],
        write: ['_queues']
      },
      action: function () {
        if (db._queues.exists(key)) {
          db._queues.delete(key);
          result = true;
        }
      }
    });
    return result;
  },
  registerJobType: function (type, opts) {
    'use strict';
    if (typeof opts === 'function') {
      opts = {execute: opts};
    }
    if (typeof opts.execute !== 'function') {
      throw new Error('Must provide a function to execute!');
    }
    var cfg = _.extend({maxFailures: 0}, opts);
    if (cfg.maxFailures < 0) {
      cfg.maxFailures = Infinity;
    }
    queues._jobTypes[type] = cfg;
  }
};

Queue = function Queue(name) {
  'use strict';
  Object.defineProperty(this, 'name', {
    get: function () {
      return name;
    },
    set: function (value) {
      throw new Error('name is not mutable');
    },
    configurable: false,
    enumerable: true
  });
};

Queue.prototype.push = function (type, data) {
  'use strict';
  if (typeof type !== 'string') {
    throw new Error('Must pass a job type!');
  }
  db._jobs.save({
    status: 'pending',
    queue: this.name,
    type: type,
    failures: 0,
    data: data
  });
};

queues._worker = {
  work: function (job) {
    'use strict';
    var db = require('org/arangodb').db,
      queues = require('org/arangodb/foxx').queues,
      console = require('console'),
      cfg = queues._jobTypes[job.type],
      failed = false;

    if (!cfg) {
      console.warn('Unknown job type for job ' + job._key + ':', job.type);
      db._jobs.update(job._key, {status: 'pending'});
      return;
    }

    try {
      cfg.execute(job.data);
    } catch (err) {
      console.error('Job ' + job._key + ' failed:', err);
      failed = true;
    }
    db._jobs.update(job._key, failed ? {
      failures: job.failures + 1,
      status: (job.failures + 1 > cfg.maxFailures) ? 'failed' : 'pending'
    } : {status: 'complete'});
  }
};

queues._manager = {
  manage: function () {
    'use strict';
    var _ = require('underscore'),
      db = require('org/arangodb').db,
      queues = require('org/arangodb/foxx').queues,
      tasks = require('org/arangodb/tasks');

    db._executeTransaction({
      collections: {
        read: ['_queues', '_jobs'],
        write: ['_jobs']
      },
      action: function () {
        db._queues.all().toArray().forEach(function (queue) {
          var numBusy = db._jobs.byExample({
            queue: queue._key,
            status: 'progress'
          }).count();
          if (numBusy >= queue.maxWorkers) {
            return;
          }
          db._jobs.byExample({
            queue: queue._key,
            status: 'pending'
          }).limit(queue.maxWorkers - numBusy).toArray().forEach(function (doc) {
            db._jobs.update(doc, {status: 'progress'});
            tasks.register({
              command: queues._worker.work,
              params: _.extend({}, doc, {status: 'progress'}),
              offset: 0
            });
          });
        });
      }
    });
  },
  run: function () {
    'use strict';
    db._jobs.updateByExample({status: 'progress'}, {status: 'pending'});
    tasks.register({command: queues._manager.manage, period: QUEUE_MANAGER_PERIOD / 1000});
  }
};

queues.create('default', 1);

module.exports = queues;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint"
// End:
<|endoftext|>


<commit_before>import Component from '../../common/Component';
import Link from '../../common/components/Link';
import avatar from '../../common/helpers/avatar';
import listItems from '../../common/helpers/listItems';
import highlight from '../../common/helpers/highlight';
import icon from '../../common/helpers/icon';
import humanTime from '../../common/utils/humanTime';
import ItemList from '../../common/utils/ItemList';
import abbreviateNumber from '../../common/utils/abbreviateNumber';
import Dropdown from '../../common/components/Dropdown';
import TerminalPost from './TerminalPost';
import SubtreeRetainer from '../../common/utils/SubtreeRetainer';
import DiscussionControls from '../utils/DiscussionControls';
import slidable from '../utils/slidable';
import extractText from '../../common/utils/extractText';
import classList from '../../common/utils/classList';
import DiscussionPage from './DiscussionPage';

import { escapeRegExp } from 'lodash-es';
/**
 * The `DiscussionListItem` component shows a single discussion in the
 * discussion list.
 *
 * ### Attrs
 *
 * - `discussion`
 * - `params`
 */
export default class DiscussionListItem extends Component {
  oninit(vnode) {
    super.oninit(vnode);

    /**
     * Set up a subtree retainer so that the discussion will not be redrawn
     * unless new data comes in.
     *
     * @type {SubtreeRetainer}
     */
    this.subtree = new SubtreeRetainer(
      () => this.attrs.discussion.freshness,
      () => {
        const time = app.session.user && app.session.user.markedAllAsReadAt();
        return time && time.getTime();
      },
      () => this.active()
    );
  }

  elementAttrs() {
    return {
      className: classList([
        'DiscussionListItem',
        this.active() ? 'active' : '',
        this.attrs.discussion.isHidden() ? 'DiscussionListItem--hidden' : '',
        'ontouchstart' in window ? 'Slidable' : '',
      ]),
    };
  }

  view() {
    const discussion = this.attrs.discussion;
    const user = discussion.user();
    const isUnread = discussion.isUnread();
    const isRead = discussion.isRead();
    const showUnread = !this.showRepliesCount() && isUnread;
    let jumpTo = 0;
    const controls = DiscussionControls.controls(discussion, this).toArray();
    const attrs = this.elementAttrs();

    if (this.attrs.params.q) {
      const post = discussion.mostRelevantPost();
      if (post) {
        jumpTo = post.number();
      }

      const phrase = escapeRegExp(this.attrs.params.q);
      this.highlightRegExp = new RegExp(phrase + '|' + phrase.trim().replace(/\s+/g, '|'), 'gi');
    } else {
      jumpTo = Math.min(discussion.lastPostNumber(), (discussion.lastReadPostNumber() || 0) + 1);
    }

    return (

        {controls.length
          ? Dropdown.component(
              {
                icon: 'fas fa-ellipsis-v',
                className: 'DiscussionListItem-controls',
                buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right',
              },
              controls
            )
          : ''}

          {icon('fas fa-check')}

          <Link
            href={user ? app.route.user(user) : '#'}
            className="DiscussionListItem-author"
            title={extractText(
              app.translator.trans('core.forum.discussion_list.started_text', { user: user, ago: humanTime(discussion.createdAt()) })
            )}
            oncreate={function (vnode) {
              $(vnode.dom).tooltip({ placement: 'right' });
            }}
          >
            {avatar(user, { title: '' })}
          </Link>

{listItems(discussion.badges().toArray())}

          <Link href={app.route.discussion(discussion, jumpTo)} className="DiscussionListItem-main">

{highlight(discussion.title(), this.highlightRegExp)}

{listItems(this.infoItems().toArray())}

          </Link>

            {abbreviateNumber(discussion[showUnread ? 'unreadCount' : 'replyCount']())}

    );
  }

  oncreate(vnode) {
    super.oncreate(vnode);

    // If we're on a touch device, set up the discussion row to be slidable.
    // This allows the user to drag the row to either side of the screen to
    // reveal controls.
    if ('ontouchstart' in window) {
      const slidableInstance = slidable(this.$());

      this.$('.DiscussionListItem-controls').on('hidden.bs.dropdown', () => slidableInstance.reset());
    }
  }

  onbeforeupdate(vnode, old) {
    super.onbeforeupdate(vnode, old);

    return this.subtree.needsRebuild();
  }

  /**
   * Determine whether or not the discussion is currently being viewed.
   *
   * @return {Boolean}
   */
  active() {
    return app.current.matches(DiscussionPage, { discussion: this.attrs.discussion });
  }

  /**
   * Determine whether or not information about who started the discussion
   * should be displayed instead of information about the most recent reply to
   * the discussion.
   *
   * @return {Boolean}
   */
  showFirstPost() {
    return ['newest', 'oldest'].indexOf(this.attrs.params.sort) !== -1;
  }

  /**
   * Determine whether or not the number of replies should be shown instead of
   * the number of unread posts.
   *
   * @return {Boolean}
   */
  showRepliesCount() {
    return this.attrs.params.sort === 'replies';
  }

  /**
   * Mark the discussion as read.
   */
  markAsRead() {
    const discussion = this.attrs.discussion;

    if (discussion.isUnread()) {
      discussion.save({ lastReadPostNumber: discussion.lastPostNumber() });
      m.redraw();
    }
  }

  /**
   * Build an item list of info for a discussion listing. By default this is
   * just the first/last post indicator.
   *
   * @return {ItemList}
   */
  infoItems() {
    const items = new ItemList();

    if (this.attrs.params.q) {
      const post = this.attrs.discussion.mostRelevantPost() || this.attrs.discussion.firstPost();

      if (post && post.contentType() === 'comment') {
        const excerpt = highlight(post.contentPlain(), this.highlightRegExp, 175);
        items.add('excerpt', excerpt, -100);
      }
    } else {
      items.add(
        'terminalPost',
        TerminalPost.component({
          discussion: this.attrs.discussion,
          lastPost: !this.showFirstPost(),
        })
      );
    }

    return items;
  }
}
<commit_msg>Add missing a11y attributes (#2564)<commit_after>import Component from '../../common/Component';
import Link from '../../common/components/Link';
import avatar from '../../common/helpers/avatar';
import listItems from '../../common/helpers/listItems';
import highlight from '../../common/helpers/highlight';
import icon from '../../common/helpers/icon';
import humanTime from '../../common/utils/humanTime';
import ItemList from '../../common/utils/ItemList';
import abbreviateNumber from '../../common/utils/abbreviateNumber';
import Dropdown from '../../common/components/Dropdown';
import TerminalPost from './TerminalPost';
import SubtreeRetainer from '../../common/utils/SubtreeRetainer';
import DiscussionControls from '../utils/DiscussionControls';
import slidable from '../utils/slidable';
import extractText from '../../common/utils/extractText';
import classList from '../../common/utils/classList';
import DiscussionPage from './DiscussionPage';

import { escapeRegExp } from 'lodash-es';
/**
 * The `DiscussionListItem` component shows a single discussion in the
 * discussion list.
 *
 * ### Attrs
 *
 * - `discussion`
 * - `params`
 */
export default class DiscussionListItem extends Component {
  oninit(vnode) {
    super.oninit(vnode);

    /**
     * Set up a subtree retainer so that the discussion will not be redrawn
     * unless new data comes in.
     *
     * @type {SubtreeRetainer}
     */
    this.subtree = new SubtreeRetainer(
      () => this.attrs.discussion.freshness,
      () => {
        const time = app.session.user && app.session.user.markedAllAsReadAt();
        return time && time.getTime();
      },
      () => this.active()
    );
  }

  elementAttrs() {
    return {
      className: classList([
        'DiscussionListItem',
        this.active() ? 'active' : '',
        this.attrs.discussion.isHidden() ? 'DiscussionListItem--hidden' : '',
        'ontouchstart' in window ? 'Slidable' : '',
      ]),
    };
  }

  view() {
    const discussion = this.attrs.discussion;
    const user = discussion.user();
    const isUnread = discussion.isUnread();
    const isRead = discussion.isRead();
    const showUnread = !this.showRepliesCount() && isUnread;
    let jumpTo = 0;
    const controls = DiscussionControls.controls(discussion, this).toArray();
    const attrs = this.elementAttrs();

    if (this.attrs.params.q) {
      const post = discussion.mostRelevantPost();
      if (post) {
        jumpTo = post.number();
      }

      const phrase = escapeRegExp(this.attrs.params.q);
      this.highlightRegExp = new RegExp(phrase + '|' + phrase.trim().replace(/\s+/g, '|'), 'gi');
    } else {
      jumpTo = Math.min(discussion.lastPostNumber(), (discussion.lastReadPostNumber() || 0) + 1);
    }

    return (

        {controls.length
          ? Dropdown.component(
              {
                icon: 'fas fa-ellipsis-v',
                className: 'DiscussionListItem-controls',
                buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right',
              },
              controls
            )
          : ''}

          {icon('fas fa-check')}

          <Link
            href={user ? app.route.user(user) : '#'}
            className="DiscussionListItem-author"
            title={extractText(
              app.translator.trans('core.forum.discussion_list.started_text', { user: user, ago: humanTime(discussion.createdAt()) })
            )}
            oncreate={function (vnode) {
              $(vnode.dom).tooltip({ placement: 'right' });
            }}
          >
            {avatar(user, { title: '' })}
          </Link>

{listItems(discussion.badges().toArray())}

          <Link href={app.route.discussion(discussion, jumpTo)} className="DiscussionListItem-main">

{highlight(discussion.title(), this.highlightRegExp)}

{listItems(this.infoItems().toArray())}

          </Link>

            {abbreviateNumber(discussion[showUnread ? 'unreadCount' : 'replyCount']())}

    );
  }

  oncreate(vnode) {
    super.oncreate(vnode);

    // If we're on a touch device, set up the discussion row to be slidable.
    // This allows the user to drag the row to either side of the screen to
    // reveal controls.
    if ('ontouchstart' in window) {
      const slidableInstance = slidable(this.$());

      this.$('.DiscussionListItem-controls').on('hidden.bs.dropdown', () => slidableInstance.reset());
    }
  }

  onbeforeupdate(vnode, old) {
    super.onbeforeupdate(vnode, old);

    return this.subtree.needsRebuild();
  }

  /**
   * Determine whether or not the discussion is currently being viewed.
   *
   * @return {Boolean}
   */
  active() {
    return app.current.matches(DiscussionPage, { discussion: this.attrs.discussion });
  }

  /**
   * Determine whether or not information about who started the discussion
   * should be displayed instead of information about the most recent reply to
   * the discussion.
   *
   * @return {Boolean}
   */
  showFirstPost() {
    return ['newest', 'oldest'].indexOf(this.attrs.params.sort) !== -1;
  }

  /**
   * Determine whether or not the number of replies should be shown instead of
   * the number of unread posts.
   *
   * @return {Boolean}
   */
  showRepliesCount() {
    return this.attrs.params.sort === 'replies';
  }

  /**
   * Mark the discussion as read.
   */
  markAsRead() {
    const discussion = this.attrs.discussion;

    if (discussion.isUnread()) {
      discussion.save({ lastReadPostNumber: discussion.lastPostNumber() });
      m.redraw();
    }
  }

  /**
   * Build an item list of info for a discussion listing. By default this is
   * just the first/last post indicator.
   *
   * @return {ItemList}
   */
  infoItems() {
    const items = new ItemList();

    if (this.attrs.params.q) {
      const post = this.attrs.discussion.mostRelevantPost() || this.attrs.discussion.firstPost();

      if (post && post.contentType() === 'comment') {
        const excerpt = highlight(post.contentPlain(), this.highlightRegExp, 175);
        items.add('excerpt', excerpt, -100);
      }
    } else {
      items.add(
        'terminalPost',
        TerminalPost.component({
          discussion: this.attrs.discussion,
          lastPost: !this.showFirstPost(),
        })
      );
    }

    return items;
  }
}
<|endoftext|>


<commit_before>// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.

define([
    'base/js/namespace',
    'jquery',
    'base/js/utils',
    'base/js/dialog',
], function(IPython, $, utils, dialog) {
    var Contents = function(options) {
        // Constructor
        //
        // A contents handles passing file operations
        // to the back-end.  This includes checkpointing
        // with the normal file operations.
        //
        // Parameters:
        //  options: dictionary
        //      Dictionary of keyword arguments.
        //          base_url: string
        this.base_url = options.base_url;
    };

    /** Error type */
    Contents.DIRECTORY_NOT_EMPTY_ERROR = 'DirectoryNotEmptyError';

    Contents.DirectoryNotEmptyError = function() {
        // Constructor
        //
        // An error representing the result of attempting to delete a non-empty
        // directory.
        this.message = 'A directory must be empty before being deleted.';
    }
    Contents.DirectoryNotEmptyError.prototype = new Error;
    Contents.DirectoryNotEmptyError.prototype.name =
        Contents.DIRECTORY_NOT_EMPTY_ERROR;

    Contents.prototype.api_url = function() {
        var url_parts = [this.base_url, 'api/contents'].concat(
                                Array.prototype.slice.apply(arguments));
        return utils.url_join_encode.apply(null, url_parts);
    };

    /**
     * Creates a basic error handler that wraps a jqXHR error as an Error.
     *
     * Takes a callback that accepts an Error, and returns a callback that can
     * be passed directly to $.ajax, which will wrap the error from jQuery
     * as an Error, and pass that to the original callback.
     *
     * @method create_basic_error_handler
     * @param{Function} callback
     * @return{Function}
     */
    Contents.prototype.create_basic_error_handler = function(callback) {
        if (!callback) {
            return function(xhr, status, error) { };
        }
        return function(xhr, status, error) {
            callback(utils.wrap_ajax_error(xhr, status, error));
        };
    }

    /**
     * File Functions (including notebook operations)
     */

    /**
     * Load a file.
     *
     * Calls success with file JSON model, or error with error.
     *
     * @method load_notebook
     * @param {String} path
     * @param {String} name
     * @param {Function} success
     * @param {Function} error
     */
    Contents.prototype.load_file = function (path, name, options) {
        // We do the call with settings so we can set cache to false.
        var settings = {
            processData : false,
            cache : false,
            type : "GET",
            dataType : "json",
            success : options.success,
            error : this.create_basic_error_handler(options.error)
        };
        var url = this.api_url(path, name);
        $.ajax(url, settings);
    };

    /**
     * Creates a new notebook file at the specified directory path.
     *
     * @method scroll_to_cell
     * @param {String} path The path to create the new notebook at
     */
    Contents.prototype.new_notebook = function(path, options) {
        var error = options.error || function() {};
        var settings = {
            processData : false,
            cache : false,
            type : "POST",
            dataType : "json",
            success : options.success || function() {},
            error : this.create_basic_error_handler(options.error)
        };
        $.ajax(this.api_url(path), settings);
    };

    Contents.prototype.delete_file = function(name, path, options) {
        var error = options.error || function() {};
        var that = this;
        var settings = {
            processData : false,
            cache : false,
            type : "DELETE",
            dataType : "json",
            success : options.success || function() {},
            error : function(xhr, status, error) {
                // TODO: update IPEP27 to specify errors more precisely, so
                // that error types can be detected here with certainty.
                if (xhr.status === 400) {
                    error(new Contents.DirectoryNotEmptyError());
                }
                error(utils.wrap_ajax_error(xhr, status, error));
            }
        };
        var url = this.api_url(path, name);
        $.ajax(url, settings);
    };

    Contents.prototype.rename_file = function(path, name, new_path, new_name, options) {
        var data = {name: new_name, path: new_path};
        var settings = {
            processData : false,
            cache : false,
            type : "PATCH",
            data : JSON.stringify(data),
            dataType: "json",
            contentType: 'application/json',
            success : options.success || function() {},
            error : this.create_basic_error_handler(options.error)
        };
        var url = this.api_url(path, name);
        $.ajax(url, settings);
    };

    Contents.prototype.save_file = function(path, name, model, options) {
        // We do the call with settings so we can set cache to false.
        var settings = {
            processData : false,
            cache : false,
            type : "PUT",
            data : JSON.stringify(model),
            contentType: 'application/json',
            success : options.success || function() {},
            error : this.create_basic_error_handler(options.error)
        };
        if (options.extra_settings) {
            $.extend(settings, options.extra_settings);
        }
        var url = this.api_url(path, name);
        $.ajax(url, settings);
    };

    /**
     * Checkpointing Functions
     */

    Contents.prototype.create_checkpoint = function(path, name, options) {
        var url = this.api_url(path, name, 'checkpoints');
        var settings = {
            type : "POST",
            success: options.success || function() {},
            error : this.create_basic_error_handler(options.error)
        };
        $.ajax(url, settings);
    };

    Contents.prototype.list_checkpoints = function(path, name, options) {
        var url = this.api_url(path, name, 'checkpoints');
        var settings = {
            type : "GET",
            success: options.success,
            error : this.create_basic_error_handler(options.error)
        };
        $.ajax(url, settings);
    };

    Contents.prototype.restore_checkpoint = function(path, name, checkpoint_id, options) {
        var url = this.api_url(path, name, 'checkpoints', checkpoint_id);
        var settings = {
            type : "POST",
            success: options.success || function() {},
            error : this.create_basic_error_handler(options.error)
        };
        $.ajax(url, settings);
    };

    Contents.prototype.delete_checkpoint = function(path, name, checkpoint_id, options) {
        var url = this.api_url(path, name, 'checkpoints', checkpoint_id);
        var settings = {
            type : "DELETE",
            success: options.success || function() {},
            error : this.create_basic_error_handler(options.error)
        };
        $.ajax(url, settings);
    };

    /**
     * File management functions
     */

    /**
     * List notebooks and directories at a given path
     *
     * On success, load_callback is called with an array of dictionaries
     * representing individual files or directories.  Each dictionary has
     * the keys:
     *     type: "notebook" or "directory"
     *     name: the name of the file or directory
     *     created: created date
     *     last_modified: last modified dat
     *     path: the path
     * @method list_notebooks
     * @param {String} path The path to list notebooks in
     * @param {Function} load_callback called with list of notebooks on success
     * @param {Function} error called with ajax results on error
     */
    Contents.prototype.list_contents = function(path, options) {
        var settings = {
            processData : false,
            cache : false,
            type : "GET",
            dataType : "json",
            success : options.success,
            error : this.create_basic_error_handler(options.error)
        };

        $.ajax(this.api_url(path), settings);
    };

    IPython.Contents = Contents;

    return {'Contents': Contents};
});
<commit_msg>Fix error callback when deleting file<commit_after>// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.

define([
    'base/js/namespace',
    'jquery',
    'base/js/utils',
    'base/js/dialog',
], function(IPython, $, utils, dialog) {
    var Contents = function(options) {
        // Constructor
        //
        // A contents handles passing file operations
        // to the back-end.  This includes checkpointing
        // with the normal file operations.
        //
        // Parameters:
        //  options: dictionary
        //      Dictionary of keyword arguments.
        //          base_url: string
        this.base_url = options.base_url;
    };

    /** Error type */
    Contents.DIRECTORY_NOT_EMPTY_ERROR = 'DirectoryNotEmptyError';

    Contents.DirectoryNotEmptyError = function() {
        // Constructor
        //
        // An error representing the result of attempting to delete a non-empty
        // directory.
        this.message = 'A directory must be empty before being deleted.';
    }
    Contents.DirectoryNotEmptyError.prototype = new Error;
    Contents.DirectoryNotEmptyError.prototype.name =
        Contents.DIRECTORY_NOT_EMPTY_ERROR;

    Contents.prototype.api_url = function() {
        var url_parts = [this.base_url, 'api/contents'].concat(
                                Array.prototype.slice.apply(arguments));
        return utils.url_join_encode.apply(null, url_parts);
    };

    /**
     * Creates a basic error handler that wraps a jqXHR error as an Error.
     *
     * Takes a callback that accepts an Error, and returns a callback that can
     * be passed directly to $.ajax, which will wrap the error from jQuery
     * as an Error, and pass that to the original callback.
     *
     * @method create_basic_error_handler
     * @param{Function} callback
     * @return{Function}
     */
    Contents.prototype.create_basic_error_handler = function(callback) {
        if (!callback) {
            return function(xhr, status, error) { };
        }
        return function(xhr, status, error) {
            callback(utils.wrap_ajax_error(xhr, status, error));
        };
    }

    /**
     * File Functions (including notebook operations)
     */

    /**
     * Load a file.
     *
     * Calls success with file JSON model, or error with error.
     *
     * @method load_notebook
     * @param {String} path
     * @param {String} name
     * @param {Function} success
     * @param {Function} error
     */
    Contents.prototype.load_file = function (path, name, options) {
        // We do the call with settings so we can set cache to false.
        var settings = {
            processData : false,
            cache : false,
            type : "GET",
            dataType : "json",
            success : options.success,
            error : this.create_basic_error_handler(options.error)
        };
        var url = this.api_url(path, name);
        $.ajax(url, settings);
    };

    /**
     * Creates a new notebook file at the specified directory path.
     *
     * @method scroll_to_cell
     * @param {String} path The path to create the new notebook at
     */
    Contents.prototype.new_notebook = function(path, options) {
        var error = options.error || function() {};
        var settings = {
            processData : false,
            cache : false,
            type : "POST",
            dataType : "json",
            success : options.success || function() {},
            error : this.create_basic_error_handler(options.error)
        };
        $.ajax(this.api_url(path), settings);
    };

    Contents.prototype.delete_file = function(name, path, options) {
        var error_callback = options.error || function() {};
        var that = this;
        var settings = {
            processData : false,
            cache : false,
            type : "DELETE",
            dataType : "json",
            success : options.success || function() {},
            error : function(xhr, status, error) {
                // TODO: update IPEP27 to specify errors more precisely, so
                // that error types can be detected here with certainty.
                if (xhr.status === 400) {
                    error_callback(new Contents.DirectoryNotEmptyError());
                }
                error_callback(utils.wrap_ajax_error(xhr, status, error));
            }
        };
        var url = this.api_url(path, name);
        $.ajax(url, settings);
    };

    Contents.prototype.rename_file = function(path, name, new_path, new_name, options) {
        var data = {name: new_name, path: new_path};
        var settings = {
            processData : false,
            cache : false,
            type : "PATCH",
            data : JSON.stringify(data),
            dataType: "json",
            contentType: 'application/json',
            success : options.success || function() {},
            error : this.create_basic_error_handler(options.error)
        };
        var url = this.api_url(path, name);
        $.ajax(url, settings);
    };

    Contents.prototype.save_file = function(path, name, model, options) {
        // We do the call with settings so we can set cache to false.
        var settings = {
            processData : false,
            cache : false,
            type : "PUT",
            data : JSON.stringify(model),
            contentType: 'application/json',
            success : options.success || function() {},
            error : this.create_basic_error_handler(options.error)
        };
        if (options.extra_settings) {
            $.extend(settings, options.extra_settings);
        }
        var url = this.api_url(path, name);
        $.ajax(url, settings);
    };

    /**
     * Checkpointing Functions
     */

    Contents.prototype.create_checkpoint = function(path, name, options) {
        var url = this.api_url(path, name, 'checkpoints');
        var settings = {
            type : "POST",
            success: options.success || function() {},
            error : this.create_basic_error_handler(options.error)
        };
        $.ajax(url, settings);
    };

    Contents.prototype.list_checkpoints = function(path, name, options) {
        var url = this.api_url(path, name, 'checkpoints');
        var settings = {
            type : "GET",
            success: options.success,
            error : this.create_basic_error_handler(options.error)
        };
        $.ajax(url, settings);
    };

    Contents.prototype.restore_checkpoint = function(path, name, checkpoint_id, options) {
        var url = this.api_url(path, name, 'checkpoints', checkpoint_id);
        var settings = {
            type : "POST",
            success: options.success || function() {},
            error : this.create_basic_error_handler(options.error)
        };
        $.ajax(url, settings);
    };

    Contents.prototype.delete_checkpoint = function(path, name, checkpoint_id, options) {
        var url = this.api_url(path, name, 'checkpoints', checkpoint_id);
        var settings = {
            type : "DELETE",
            success: options.success || function() {},
            error : this.create_basic_error_handler(options.error)
        };
        $.ajax(url, settings);
    };

    /**
     * File management functions
     */

    /**
     * List notebooks and directories at a given path
     *
     * On success, load_callback is called with an array of dictionaries
     * representing individual files or directories.  Each dictionary has
     * the keys:
     *     type: "notebook" or "directory"
     *     name: the name of the file or directory
     *     created: created date
     *     last_modified: last modified dat
     *     path: the path
     * @method list_notebooks
     * @param {String} path The path to list notebooks in
     * @param {Function} load_callback called with list of notebooks on success
     * @param {Function} error called with ajax results on error
     */
    Contents.prototype.list_contents = function(path, options) {
        var settings = {
            processData : false,
            cache : false,
            type : "GET",
            dataType : "json",
            success : options.success,
            error : this.create_basic_error_handler(options.error)
        };

        $.ajax(this.api_url(path), settings);
    };

    IPython.Contents = Contents;

    return {'Contents': Contents};
});
<|endoftext|>


<commit_before>$p.changeMultiSelect = function ($control) {
    $p.setData($control);
    if ($control.hasClass('auto-postback') && !$control.hasClass('no-postback')) {
        $p.send($control, $p.getFormId($control));
    }
    $control.removeClass('no-postback');
}

$p.setMultiSelectData = function ($control) {
    $p.getDataByInnerElement($control)[$control.attr('id')] = JSON.stringify(
        $('[name="multiselect_' + $control.attr('id') + '"]')
            .filter(function () { return $(this).prop('checked'); })
            .map(function () { return $(this).val() })
            .toArray());
}<commit_msg>Refactored: formIdの指定を省略。<commit_after>$p.changeMultiSelect = function ($control) {
    $p.setData($control);
    if ($control.hasClass('auto-postback') && !$control.hasClass('no-postback')) {
        $p.send($control);
    }
    $control.removeClass('no-postback');
}

$p.setMultiSelectData = function ($control) {
    $p.getDataByInnerElement($control)[$control.attr('id')] = JSON.stringify(
        $('[name="multiselect_' + $control.attr('id') + '"]')
            .filter(function () { return $(this).prop('checked'); })
            .map(function () { return $(this).val() })
            .toArray());
}<|endoftext|>


<commit_before>$(document).ready(function () {

  "use strict";

  /* Permitting & violations */

  var ctx2 = $("#openPermits").get(0).getContext("2d"),
      permitJSON = JSON.parse($("#permits_rawjson")[0].childNodes[0].data),
      violationsJSON = JSON.parse($("#violations_rawjson")[0].childNodes[0].data),
      series2 = [],
      datetime2 = [],
      series3 = [],
      datetime3 = [],
      openPermitData,
      openPermitDataset,
      openPermitChart;

  buildOpenPermitChart();

  function buildOpenPermitChart() {

    for(var i = 0; i < permitJSON.length; i++) {

      series2.push(permitJSON[i].total);
      datetime2.push(prettyDates((permitJSON[i].month).split('T')[0]));

    }

    series2.reverse();
    datetime2.reverse();

    openPermitDataset = {
        labels:datetime2,
        datasets: [
          {
              fillColor: globalColors.t_orange,
              strokeColor: globalColors.orange,
              pointColor: globalColors.orange,
              pointStrokeColor: "#fff",
              pointHighlightFill: "#fff",
              pointHighlightStroke: "rgba(220,220,220,1)",
              data: series2
          }
        ]
    },

    openPermitChart = new Chart(ctx2).Line(openPermitDataset);

  }

  function prettyDates(date) {

    var year = date.split('-')[0];
    var month = String(date.split('-')[1]);   //strict mode means no octal literals -- no 08 or 09 unless type string. A thing I learned today!

    switch (month) {

      case '01':
        month = 'Jan';
        break;
      case '02':
        month = 'Feb';
        break;
      case '03':
        month = 'Mar';
        break;
      case '04':
        month = 'Apr';
        break;
      case '05':
        month = 'May';
        break;
      case '06':
        month = 'Jun';
        break;
      case '07':
        month = 'Jul';
        break;
      case '08':
        month = 'Aug';
        break;
      case '09':
        month = 'Sep';
        break;
      case '10':
        month = 'Oct';
        break;
      case '11':
        month = 'Nov';
        break;
      case '12':
        month = 'Dec';
        break;

    }

    date = month + ' ' + year;
    return date;
  }

  var permitTypes = JSON.parse($("#permitstype")[0].childNodes[0].data),
      pttx = $("#permitTypeChart").get(0).getContext("2d"),
      cleanPermitData = [],
      cleanPermitLabels = [];

  //set the data up for Charts.js
  for(var i = 0; i < permitTypes.data.length; i+=1) {

    if(permitTypes.data[i].permit_type != 'CCUT') {

      cleanPermitLabels.push(humanNames(permitTypes.data[i].permit_type));
      cleanPermitData.push(permitTypes.data[i].count);
      //console.log(data[i].issue_type, i);

    }

  }

  var vdata2 = {
    labels: cleanPermitLabels,
    datasets: [
      {
        label: "Permits by Type",
        fillColor: globalColors.t_purple_1,
        strokeColor: globalColors.purple_1,
        data: cleanPermitData
      }
    ]
  };

  function humanNames(type) {

    //console.log(type);

    switch(type) {

      case 'FIRE':

        return 'Fire';
        break;

      case 'MMEC':

        return 'Municipal: Mechanical';
        break;

      case 'ZIPS':

        return 'Zoning Improvement Permit';
        break;

      case 'MELE':

        return 'Muncipal: Electrical';
        break;

      case 'CCUT':

        return 'CCUT';
        break;

      case 'ELEC':

        return 'Electrical';
        break;

      case 'PLUM':

        return 'Plumbing';
        break;

      case 'MPLU':

        return 'Municipal: Plumbing';
        break;

      case 'MBLD':

        return 'Municipal: Building';
        break;

      case 'LPGX':

        return 'Liquid Petroleum Gas';
        break;

      case 'BLDG':

        return 'Building';
        break;

      case 'MECH':

        return 'Mechanical';
        break;

    }

  }

  $('#permitTypeChart').parent().parent().find('.headline').html(permitTypes.title);
  var barChart2 = new Chart(pttx).HorizontalBar(vdata2),

      plumbingChart = $("#p-plumbing").get(0).getContext("2d"),
      buildingChart = $("#p-building").get(0).getContext("2d"),
      fireChart = $("#p-fire").get(0).getContext("2d"),
      elexChart = $("#p-electrical").get(0).getContext("2d"),
      zoningChart = $("#p-zoning").get(0).getContext("2d"),
      pData = parseInt($('#plumbing-data').text()),
      bData = parseInt($('#building-data').text()),
      fData = parseInt($('#fire-data').text()),
      eData = parseInt($('#electrical-data').text()),
      zData = parseInt($('#zoning-data').text()),

      plumbingData = [
        {
          value: pData,
          color:globalColors.purple_1,
          highlight: globalColors.t_purple_1,
          label: 'Plumbing: same-day'
        },
        {
          value: (100 - pData),
          color: globalColors.purple_2,
          highlight: globalColors.t_purple_2,
          label: 'Plumbing'
        }
      ],

      buildingData = [
        {
          value: bData,
          color:globalColors.purple_1,
          highlight: globalColors.t_purple_1,
          label: 'Building: same-day'
        },
        {
          value: (100 - bData),
          color: globalColors.purple_2,
          highlight: globalColors.t_purple_2,
          label: 'Building'
        }
      ],

      fireData = [
        {
          value: fData,
          color:globalColors.purple_1,
          highlight: globalColors.t_purple_1,
          label: 'Fire: same-day'
        },
        {
          value: (100 - fData),
          color: globalColors.purple_2,
          highlight: globalColors.t_purple_2,
          label: 'Fire'
        }
      ],

      elexData = [
        {
          value: eData,
          color:globalColors.purple_1,
          highlight: globalColors.t_purple_1,
          label: 'Electrical: same-day'
        },
        {
          value: (100 - eData),
          color: globalColors.purple_2,
          highlight: globalColors.t_purple_2,
          label: 'Electrical'
        }
      ],

      zoningData = [
        {
          value: zData,
          color:globalColors.purple_1,
          highlight: globalColors.t_purple_1,
          label: 'Zoning: same-day'
        },
        {
          value: (100 - zData),
          color: globalColors.purple_2,
          highlight: globalColors.t_purple_2,
          label: 'Zoning'
        }
      ],

      pChart = new Chart(plumbingChart).Pie(plumbingData),
      bChart = new Chart(buildingChart).Pie(buildingData),
      fChart = new Chart(fireChart).Pie(fireData),
      eChart = new Chart(elexChart).Pie(elexData),
      zChart = new Chart(zoningChart).Pie(zoningData);

  /***************************** tool tips *****************************/
  //called from dashboard-common.js

  initTabletop();

}); //close ready
<commit_msg>changed purple to orange on building permit metrics<commit_after>$(document).ready(function () {

  "use strict";

  /* Permitting & violations */

  var ctx2 = $("#openPermits").get(0).getContext("2d"),
      permitJSON = JSON.parse($("#permits_rawjson")[0].childNodes[0].data),
      violationsJSON = JSON.parse($("#violations_rawjson")[0].childNodes[0].data),
      series2 = [],
      datetime2 = [],
      series3 = [],
      datetime3 = [],
      openPermitData,
      openPermitDataset,
      openPermitChart;

  buildOpenPermitChart();

  function buildOpenPermitChart() {

    for(var i = 0; i < permitJSON.length; i++) {

      series2.push(permitJSON[i].total);
      datetime2.push(prettyDates((permitJSON[i].month).split('T')[0]));

    }

    series2.reverse();
    datetime2.reverse();

    openPermitDataset = {
        labels:datetime2,
        datasets: [
          {
              fillColor: globalColors.orange_20,
              strokeColor: globalColors.orange,
              pointColor: globalColors.orange,
              pointStrokeColor: "#fff",
              pointHighlightFill: "#fff",
              pointHighlightStroke: "rgba(220,220,220,1)",
              data: series2
          }
        ]
    },

    openPermitChart = new Chart(ctx2).Line(openPermitDataset);

  }

  function prettyDates(date) {

    var year = date.split('-')[0];
    var month = String(date.split('-')[1]);   //strict mode means no octal literals -- no 08 or 09 unless type string. A thing I learned today!

    switch (month) {

      case '01':
        month = 'Jan';
        break;
      case '02':
        month = 'Feb';
        break;
      case '03':
        month = 'Mar';
        break;
      case '04':
        month = 'Apr';
        break;
      case '05':
        month = 'May';
        break;
      case '06':
        month = 'Jun';
        break;
      case '07':
        month = 'Jul';
        break;
      case '08':
        month = 'Aug';
        break;
      case '09':
        month = 'Sep';
        break;
      case '10':
        month = 'Oct';
        break;
      case '11':
        month = 'Nov';
        break;
      case '12':
        month = 'Dec';
        break;

    }

    date = month + ' ' + year;
    return date;
  }

  var permitTypes = JSON.parse($("#permitstype")[0].childNodes[0].data),
      pttx = $("#permitTypeChart").get(0).getContext("2d"),
      cleanPermitData = [],
      cleanPermitLabels = [];

  //set the data up for Charts.js
  for(var i = 0; i < permitTypes.data.length; i+=1) {

    if(permitTypes.data[i].permit_type != 'CCUT') {

      cleanPermitLabels.push(humanNames(permitTypes.data[i].permit_type));
      cleanPermitData.push(permitTypes.data[i].count);
      //console.log(data[i].issue_type, i);

    }

  }

  var vdata2 = {
    labels: cleanPermitLabels,
    datasets: [
      {
        label: "Permits by Type",
        fillColor: globalColors.orange_20,
        strokeColor: globalColors.orange,
        data: cleanPermitData
      }
    ]
  };

  function humanNames(type) {

    //console.log(type);

    switch(type) {

      case 'FIRE':

        return 'Fire';
        break;

      case 'MMEC':

        return 'Municipal: Mechanical';
        break;

      case 'ZIPS':

        return 'Zoning Improvement Permit';
        break;

      case 'MELE':

        return 'Muncipal: Electrical';
        break;

      case 'CCUT':

        return 'CCUT';
        break;

      case 'ELEC':

        return 'Electrical';
        break;

      case 'PLUM':

        return 'Plumbing';
        break;

      case 'MPLU':

        return 'Municipal: Plumbing';
        break;

      case 'MBLD':

        return 'Municipal: Building';
        break;

      case 'LPGX':

        return 'Liquid Petroleum Gas';
        break;

      case 'BLDG':

        return 'Building';
        break;

      case 'MECH':

        return 'Mechanical';
        break;

    }

  }

  $('#permitTypeChart').parent().parent().find('.headline').html(permitTypes.title);
  var barChart2 = new Chart(pttx).HorizontalBar(vdata2),

      plumbingChart = $("#p-plumbing").get(0).getContext("2d"),
      buildingChart = $("#p-building").get(0).getContext("2d"),
      fireChart = $("#p-fire").get(0).getContext("2d"),
      elexChart = $("#p-electrical").get(0).getContext("2d"),
      zoningChart = $("#p-zoning").get(0).getContext("2d"),
      pData = parseInt($('#plumbing-data').text()),
      bData = parseInt($('#building-data').text()),
      fData = parseInt($('#fire-data').text()),
      eData = parseInt($('#electrical-data').text()),
      zData = parseInt($('#zoning-data').text()),

      plumbingData = [
        {
          value: pData,
          color:globalColors.orange,
          highlight: globalColors.orange_20,
          label: 'Plumbing: same-day'
        },
        {
          value: (100 - pData),
          color: globalColors.orange_60,
          highlight: globalColors.orange_20,
          label: 'Plumbing'
        }
      ],

      buildingData = [
        {
          value: bData,
          color:globalColors.orange,
          highlight: globalColors.orange_20,
          label: 'Building: same-day'
        },
        {
          value: (100 - bData),
          color: globalColors.orange_60,
          highlight: globalColors.orange_20,
          label: 'Building'
        }
      ],

      fireData = [
        {
          value: fData,
          color:globalColors.orange,
          highlight: globalColors.orange_20,
          label: 'Fire: same-day'
        },
        {
          value: (100 - fData),
          color: globalColors.orange_60,
          highlight: globalColors.orange_20,
          label: 'Fire'
        }
      ],

      elexData = [
        {
          value: eData,
          color:globalColors.orange,
          highlight: globalColors.orange_20,
          label: 'Electrical: same-day'
        },
        {
          value: (100 - eData),
          color: globalColors.orange_60,
          highlight: globalColors.orange_20,
          label: 'Electrical'
        }
      ],

      zoningData = [
        {
          value: zData,
          color:globalColors.orange,
          highlight: globalColors.orange_20,
          label: 'Zoning: same-day'
        },
        {
          value: (100 - zData),
          color: globalColors.orange_60,
          highlight: globalColors.orange_20,
          label: 'Zoning'
        }
      ],

      pChart = new Chart(plumbingChart).Pie(plumbingData),
      bChart = new Chart(buildingChart).Pie(buildingData),
      fChart = new Chart(fireChart).Pie(fireData),
      eChart = new Chart(elexChart).Pie(elexData),
      zChart = new Chart(zoningChart).Pie(zoningData);

  /***************************** tool tips *****************************/
  //called from dashboard-common.js

  initTabletop();

}); //close ready
<|endoftext|>


<commit_before>// Send reply to client.
// We expect, to have:
// - env.res
//   - body
//   - headers

'use strict';

const http        = require('http');
const compression = require('compression')();

let valid_headers = {};
[ // http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
  'Access-Control-Allow-Origin',
  'Accept-Ranges',
  'Age',
  'Allow',
  'Cache-Control',
  'Connection',
  'Content-Encoding',
  'Content-Language',
  'Content-Length',
  'Content-Location',
  'Content-MD5',
  'Content-Disposition',
  'Content-Security-Policy',
  'Content-Security-Policy-Report-Only',
  'Content-Range',
  'Content-Type',
  'Date',
  'ETag',
  'Expires',
  'Last-Modified',
  'Link',
  'Location',
  'P3P',
  'Pragma',
  'Proxy-Authenticate',
  'Refresh',
  'Retry-After',
  'Server',
  'Set-Cookie',
  'Strict-Transport-Security',
  'Trailer',
  'Transfer-Encoding',
  'Vary',
  'Via',
  'Warning',
  'WWW-Authenticate',
  // Common non-standard response headers
  'X-Frame-Options',
  'X-XSS-Protection',
  'X-Content-Security-Policy',
  'X-WebKit-CSP',
  'X-Content-Type-Options',
  'X-Powered-By',
  'X-UA-Compatible'
].forEach(hdr => { valid_headers[hdr] = true; });

////////////////////////////////////////////////////////////////////////////////

// Note, we also listen `responder:bin` to unify logging & error responding

const CHANNELS = [ 'responder:http', 'responder:rpc', 'responder:bin' ];

module.exports = function (N) {

  N.wire.after(CHANNELS, { priority: 100 }, function response_compress(env, callback) {
    compression(env.origin.req, env.origin.res, callback);
  });

  function report_error(env, message) {
    N.logger.error('send_reply: %s\n%s', message, JSON.stringify({
      ip:        env.req.ip,
      url:       env.origin.req.url,
      request:   env.origin.req.method,
      responder: env.req.type,
      apiPath:   env.method,
      params:    env.params
    }, null, 2));
  }

  N.wire.after(CHANNELS, { priority: 100 }, function response_send(env) {
    let res = env.origin.res,
        headers = env.headers,
        body = env.body,
        statusCode;

    // If someone already sent reply - do nothing
    if (res.finished) return;

    // Skip headers modify if has been sent (by custom sender)
    if (!res.headersSent) {
      //
      // Set some obligatory headers
      //

      headers.Server = headers.Server || 'Sansun Calakci';
      // added by node automatically
      // headers['Date'] = headers['Date'] || new Date).toUTCString();

      //
      // Remove Accept-Ranges if it wasn't explicitly set
      //

      if (!headers['Accept-Ranges']) {
        try {
          res.removeHeader('Accept-Ranges');
        } catch (err) {
          report_error(env, err.stack);
        }
      }

      //
      // set headers
      //

      for (let [ name, value ] of Object.entries(headers)) {
        // Check if header registered, since
        // one can mistype capitalization and name
        if (!valid_headers[name]) {
          report_error(env, `Got wrong header "${name}"`);
        }

        if (value === null) {
          try {
            res.removeHeader(name);
          } catch (err) {
            report_error(env, err.stack);
          }
          continue;
        }

        try {
          res.setHeader(name, value);
        } catch (err) {
          report_error(env, err.stack);
        }
      }
    }

    //
    // When body is given, it MUST be a Buffer or a String
    // (this error should not happen)
    //

    if (body && !Buffer.isBuffer(body) && typeof body !== 'string') {
      statusCode = N.io.APP_ERROR;
      body       = http.STATUS_CODES[statusCode];
      report_error(env, 'body MUST be a Buffer, String or Null/Undefined');
    }

    // set status code and send body (if any)
    res.statusCode = env.status;
    res.end(body);
  });
};
<commit_msg>lint warning fix<commit_after>// Send reply to client.
// We expect, to have:
// - env.res
//   - body
//   - headers

'use strict';

const http        = require('http');
const compression = require('compression')();

let valid_headers = {};
[ // http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
  'Access-Control-Allow-Origin',
  'Accept-Ranges',
  'Age',
  'Allow',
  'Cache-Control',
  'Connection',
  'Content-Encoding',
  'Content-Language',
  'Content-Length',
  'Content-Location',
  'Content-MD5',
  'Content-Disposition',
  'Content-Security-Policy',
  'Content-Security-Policy-Report-Only',
  'Content-Range',
  'Content-Type',
  'Date',
  'ETag',
  'Expires',
  'Last-Modified',
  'Link',
  'Location',
  'P3P',
  'Pragma',
  'Proxy-Authenticate',
  'Refresh',
  'Retry-After',
  'Server',
  'Set-Cookie',
  'Strict-Transport-Security',
  'Trailer',
  'Transfer-Encoding',
  'Vary',
  'Via',
  'Warning',
  'WWW-Authenticate',
  // Common non-standard response headers
  'X-Frame-Options',
  'X-XSS-Protection',
  'X-Content-Security-Policy',
  'X-WebKit-CSP',
  'X-Content-Type-Options',
  'X-Powered-By',
  'X-UA-Compatible'
].forEach(hdr => { valid_headers[hdr] = true; });

////////////////////////////////////////////////////////////////////////////////

// Note, we also listen `responder:bin` to unify logging & error responding

const CHANNELS = [ 'responder:http', 'responder:rpc', 'responder:bin' ];

module.exports = function (N) {

  N.wire.after(CHANNELS, { priority: 100 }, function response_compress(env, callback) {
    compression(env.origin.req, env.origin.res, callback);
  });

  function report_error(env, message) {
    N.logger.error('send_reply: %s\n%s', message, JSON.stringify({
      ip:        env.req.ip,
      url:       env.origin.req.url,
      request:   env.origin.req.method,
      responder: env.req.type,
      apiPath:   env.method,
      params:    env.params
    }, null, 2));
  }

  N.wire.after(CHANNELS, { priority: 100 }, function response_send(env) {
    let res = env.origin.res,
        headers = env.headers,
        body = env.body,
        statusCode;

    // If someone already sent reply - do nothing
    if (res.finished) return;

    // Skip headers modify if has been sent (by custom sender)
    if (!res.headersSent) {
      //
      // Set some obligatory headers
      //

      headers.Server = headers.Server || 'Sansun Calakci';
      // added by node automatically
      // headers['Date'] = headers['Date'] || new Date).toUTCString();

      //
      // Remove Accept-Ranges if it wasn't explicitly set
      //

      if (!headers['Accept-Ranges']) {
        try {
          res.removeHeader('Accept-Ranges');
        } catch (err) {
          report_error(env, err.stack);
        }
      }

      //
      // set headers
      //

      for (let [ name, value ] of Object.entries(headers)) {
        // Check if header registered, since
        // one can mistype capitalization and name
        if (!valid_headers[name]) {
          report_error(env, `Got wrong header "${name}"`);
        }

        /* eslint-disable max-depth */
        if (value === null) {
          try {
            res.removeHeader(name);
          } catch (err) {
            report_error(env, err.stack);
          }
          continue;
        }

        try {
          res.setHeader(name, value);
        } catch (err) {
          report_error(env, err.stack);
        }
      }
    }

    //
    // When body is given, it MUST be a Buffer or a String
    // (this error should not happen)
    //

    if (body && !Buffer.isBuffer(body) && typeof body !== 'string') {
      statusCode = N.io.APP_ERROR;
      body       = http.STATUS_CODES[statusCode];
      report_error(env, 'body MUST be a Buffer, String or Null/Undefined');
    }

    // set status code and send body (if any)
    res.statusCode = env.status;
    res.end(body);
  });
};
<|endoftext|>


<commit_before>// External Dependencies
var React           = require('react');
var classNames      = require('classnames');

/**
 * A container component for configurable modals
 * @class ModalContainer
 *
 * @memberOf GlobalComponents
 * @see {@link GlobalComponents}
 *
 * @type {*|Function}
 */
var ModalContainer = React.createClass({
	propTypes: {
		openWhen   : React.PropTypes.bool.isRequired,
		options    : React.PropTypes.shape({
			width    : React.PropTypes.string,
			keepOpen : React.PropTypes.bool // If this is true, clicking outside of a modal will not close the modal
		}),
		buttonText : React.PropTypes.string,
		target     : React.PropTypes.string,
		title      : React.PropTypes.string.isRequired,
		closeText  : React.PropTypes.string,
		saveText   : React.PropTypes.string
	},

	/**
	 * getInitialState
	 *
	 * @memberOf GlobalComponents.ModalContainer
	 * @return {object}
	 */
	getInitialState: function () {
		return {
			isOpen: false
		}
	},

	/**
	 * When the component mounts, an event listener is attached to the window
	 * to track keyup events, and filter them into this component's handleKeyUp function.
	 *
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {null}
	 */
	componentDidMount: function() {
		window.addEventListener('keyup', this.handleKeyUp);
	},

	/**
	 * When the component is unmounted, the window's KeyUp listener is removed
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {null}
	 */
	componentWillUnmount: function() {
		window.removeEventListener('keyup', this.handleKeyUp);
	},

	/**
	 * Open the modal and fire appropriate user functions
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @TODO this isn't being fired
	 */
	openModal: function () {
		this._callUserFunc('beforeOpen');

		this.setState({
			isOpen: true
		});

		this._callUserFunc('afterOpen');
	},

	/**
	 * Close the modal and fire appropriate user functions
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {null}
	 */
	closeModal: function () {
		this._callUserFunc('beforeClose');

		this.setState({
			isOpen: false
		});

		this._callUserFunc('afterClose');
	},

	/**
	 * Save modal and fire the appropriate user functions
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {null}
	 */
	saveModal: function () {
		this._callUserFunc('beforeSave');

		this.setState({
			isOpen: false
		});

		this._callUserFunc('afterSave');
	},

	/**
	 * Determine whether the modal is open and we should render the overlay
	 *
	 * @private
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @returns {jsx}
	 */
	_shouldRenderOverlay: function () {
		if (this.props.openWhen) {
			return (

			)
		}
	},

	/**
	 * Fire the selected user function, if it's provided
	 *
	 * @private
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @param {string} propName
	 *
	 * @return {null}
	 */
	_callUserFunc: function (propName) {
		if (typeof this.props[propName] !== 'undefined') {
			this.props[propName]();
		}
	},

	/**
	 * Determine if we should disable the button
	 *
	 * @memberOf GlobalComponents.ModalContainer
	 * @private
	 *
	 * @returns {string}
	 */
	_shouldDisableButton: function () {
		return this.props.disableSaveWhen;
	},

	/**
	 * Return save button jsx
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @returns {jsx}
	 */
	saveButton: function () {
		return <button type="button" className={"btn btn-primary"} disabled={this._shouldDisableButton()} onClick={this.saveModal}>{this.props.saveText || 'Save'}</button>;
	},

	/**
	 * Return cancel button jsx
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @returns {jsx}
	 */
	cancelButton: function () {
		return <button type="button" className="btn btn-default" onClick={this.closeModal}>{this.props.closeText || 'Cancel'}</button>;
	},

	/**
	 * A click listener attached to the modal.
	 * Every time a click happens, this function checks to see if the click was on the modal dialog,
	 * or outside of it.
	 *
	 * If the click is outside of the dialog, we close the modal without saving.
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @param event {event} The click event
	 *
	 * @return null
	 */
	handleClick: function(event) {
		var clickArea = this.refs.dialog,
			options     = this.props.options || {};
		if (!options.keepOpen && !clickArea.contains(event.target)) {
			this.closeModal();
		}
	},

	/**
	 * A keyup listener attached to the modal.
	 * Every time a key is released, this function checks to see if it was the 'ESC' key.
	 * If the 'ESC' was released, we close the modal.
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @param event {event} The keyup event
	 *
	 * @return null
	 */
	handleKeyUp: function(event) {
		if (event.keyCode === 27) {
			this.closeModal();
		}
	},

	/**
	 * Determine which, if any, buttons to display
	 *
	 * @private
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @returns {jsx}
	 */
	_buildButtons: function () {
		if (typeof this.props.showButton === 'string') {
			return (

					{this[this.props.showButton + 'Button']()}

			);
		} else if (!this.props.disableButtons) {
			return (

					{this.cancelButton()}
					{this.saveButton()}

			);
		}
	},

	/**
	 * Renders the Modal.
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {jsx}
	 */
	render: function () {
		// Set up a modal's class names
		var modalClassnames = classNames({
			modal : true,
			fade  : true,
			in    : this.props.openWhen
		});

		// Set up modal styling
		var modalStyles = {
			display: this.props.openWhen ? 'block' : 'none'
		};

		var modalDialogStyles = {
			width: this.props.options && this.props.options.width ? this.props.options.width : '600px'
		};

		var modalBodyClasses = typeof this.props.modalBodyClasses === 'undefined' ? 'modal-body' :
		'modal-body ' + this.props.modalBodyClasses;

		return (

				{this.props.openWhen && (

										<button type="button" className="close" onClick={this.closeModal}>×</button>

{this.props.title}

										{this.props.children}

									{this._buildButtons()}

						{this._shouldRenderOverlay()}

				)}

		)
	}
});

module.exports = ModalContainer;
<commit_msg>refactor and clean global ModalContainer component<commit_after>// External Dependencies
const React           = require('react');
const classNames      = require('classnames');

/**
 * A container component for configurable modals
 * @class ModalContainer
 *
 * @memberOf GlobalComponents
 * @see {@link GlobalComponents}
 *
 * @type {*|Function}
 */
const ModalContainer = React.createClass({
	propTypes: {
		openWhen        : React.PropTypes.bool.isRequired,
		title           : React.PropTypes.string.isRequired,
		closeText       : React.PropTypes.string,
		confirmText     : React.PropTypes.string,
		modalBodyClasses: React.PropTypes.string,
		beforeOpen      : React.PropTypes.func,
		afterOpen       : React.PropTypes.func,
		beforeClose     : React.PropTypes.func,
		afterClose      : React.PropTypes.func,
		beforeConfirm   : React.PropTypes.func,
		afterConfirm    : React.PropTypes.func,
		disableConfirm  : React.PropTypes.bool,
		children        : React.PropTypes.oneOfType([React.PropTypes.array, React.PropTypes.object]),
		options         : React.PropTypes.shape({
			width   : React.PropTypes.string,
			keepOpen: React.PropTypes.bool // If this is true, clicking outside of a modal will not close the modal
		})
	},

	/**
	 * getInitialState
	 *
	 * @memberOf GlobalComponents.ModalContainer
	 * @return {object}
	 */
	getInitialState: function () {
		return {
			isOpen: false
		};
	},

	/**
	 * When the component mounts, an event listener is attached to the window
	 * to track keyup events, and filter them into this component's handleKeyUp function.
	 *
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {null}
	 */
	componentDidMount: function() {
		window.addEventListener('keyup', this.handleKeyUp);
	},

	/**
	 * When the component is unmounted, the window's KeyUp listener is removed
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {null}
	 */
	componentWillUnmount: function() {
		window.removeEventListener('keyup', this.handleKeyUp);
	},

	/**
	 * Invoked when a component is receiving new props. This method is not called for the initial render.
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @param  {object} newProps [incoming props from parent component]
	 * @return {null}
	 */
	componentWillReceiveProps: function(newProps) {
		newProps.openWhen && this.openModal();
	},

	/**
	 * Open the modal and fire appropriate user functions
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {null}
	 */
	openModal: function () {
		this._modalLifeCycleEvent('open', true);
	},

	/**
	 * Close the modal and fire appropriate user functions
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {null}
	 */
	closeModal: function () {
		this._modalLifeCycleEvent('close', false);
	},

	/**
	 * Save modal and fire the appropriate user functions
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {null}
	 */
	confirmModal: function () {
		this._modalLifeCycleEvent('confirm', false);
	},

	/**
	 * Fire the before and after lifecycle hooks for the modal component.
	 *
	 * @private
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @param  {string} action         [modal lifecycle action]
	 * @param  {bool}   nextModalState [the next value for the modal `isOpen` prop]
	 *
	 * @return {null}
	 */
	_modalLifeCycleEvent: function(action, nextModalState) {
		action = action.charAt(0).toUpperCase() + action.slice(1);

		this._callUserFunc(`before${action}`);

		this.setState({
			isOpen: nextModalState
		});

		this._callUserFunc(`after${action}`);
	},

	/**
	 * Determine whether the modal is open and we should render the overlay
	 *
	 * @private
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @returns {jsx|null}
	 */
	_shouldRenderOverlay: function () {
		return this.state.isOpen &&

;
	},

	/**
	 * Fire the selected user function, if it's provided
	 *
	 * @private
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @param {string} propName  [lifecycle event for modal]
	 *
	 * @return {null}
	 */
	_callUserFunc: function (propName) {
		if (this.props[propName]) {
			this.props[propName]();
		}
	},

	/**
	 * Return save button jsx
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @returns {jsx}
	 */
	confirmButton: function () {
		return <button type="button" className={"btn btn-primary"} disabled={this.props.disableConfirm} onClick={this.confirmModal}>{this.props.confirmText || 'Confirm'}</button>;
	},

	/**
	 * Return cancel button jsx
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @returns {jsx}
	 */
	cancelButton: function () {
		return <button type="button" className="btn btn-default" onClick={this.closeModal}>{this.props.closeText || 'Cancel'}</button>;
	},

	/**
	 * A click listener attached to the modal.
	 * Every time a click happens, this function checks to see if the click was on the modal dialog,
	 * or outside of it.
	 *
	 * If the click is outside of the dialog, we close the modal without saving.
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @param {Event} event [The click event]
	 *
	 * @return {null}
	 */
	handleClick: function(event) {
		let clickArea = this.refs.dialog,
			options     = this.props.options || {};
		if (!options.keepOpen && !clickArea.contains(event.target)) {
			this.closeModal();
		}
	},

	/**
	 * A keyup listener attached to the modal.
	 * Every time a key is released, this function checks to see if it was the 'ESC' key.
	 * If the 'ESC' was released, we close the modal.
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @param {Event}  event  [The keyup event]
	 *
	 * @return {null}
	 */
	handleKeyUp: function(event) {
		if (event.keyCode === 27) {
			this.closeModal();
		}
	},

	/**
	 * Determine which, if any, buttons to display
	 *
	 * @private
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @returns {jsx}
	 */
	_buildButtons: function () {
		return (

				{this.cancelButton()}
				{this.confirmButton()}

		);
	},

	/**
	 * Set up a modal's class names
	 * @private
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {object} [CSS classes for the entire modal component]
	 */
	_modalClassnames: function() {
		return classNames({
			modal: true,
			fade : true,
			in   : this.state.isOpen
		});
	},

	/**
	 * Prepares the CSS classes for the modal body
	 *
	 * @private
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {string} [CSS classes for the modal body]
	 */
	_modalBodyClasses: function() {
		return this.props.modalBodyClasses ? 'modal-body ' + this.props.modalBodyClasses : 'modal-body';
	},

	/**
	 * Sets the width of the modal component. The default modal size is 600px.
	 *
	 * @private
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {object} [width of modal in pixels]
	 */
	_modalWidth: function() {
		return {
			width: this.props.options && this.props.options.width ? this.props.options.width : '600px'
		};
	},

	/**
	 * Render the active modal
	 *
	 * @private
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {jsx}
	 */
	_renderModal: function() {
		return (

								<button type="button" className="close" onClick={this.closeModal}>×</button>

{this.props.title}

								{this.props.children}

							{this._buildButtons()}

				{this._shouldRenderOverlay()}

		);
	},

	/**
	 * Renders the complete Modal component.
	 * @memberOf GlobalComponents.ModalContainer
	 *
	 * @return {jsx}
	 */
	render: function () {
		return (

				{this.state.isOpen && this._renderModal()}

		);
	}
});

module.exports = ModalContainer;
<|endoftext|>


<commit_before>import styled from 'react-emotion';

import { baseFonts } from '../';

export default styled('p')(baseFonts, {
  fontSize: '11px',
  display: 'block',
  textAlign: 'center',
  textTransform: 'uppercase',
  margin: 0,
  padding: 20,
});
<commit_msg>REMOVE basefonts from placeholder<commit_after>import styled from 'react-emotion';

export default styled('p')({
  fontSize: '11px',
  display: 'block',
  textAlign: 'center',
  textTransform: 'uppercase',
  margin: 0,
  padding: 20,
});
<|endoftext|>