<commit_before>//
// Copyright (C) 2012 - present Instructure, Inc.
//
// This file is part of Canvas.
//
// Canvas is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
// Software Foundation, version 3 of the License.
//
// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.

import Backbone from 'Backbone'
import _ from 'underscore'
import I18n from 'i18n!discussions.reply'
import $ from 'jquery'
import Entry from '../models/Entry'
import htmlEscape from 'str/htmlEscape'
import replyAttachmentTemplate from 'jst/discussions/_reply_attachment'
import preventDefault from '../fn/preventDefault'
import KeyboardShortcuts from '../views/editor/KeyboardShortcuts'
import stripTags from 'str/stripTags'
import RichContentEditor from 'jsx/shared/rce/RichContentEditor'
import 'jquery.instructure_forms'

RichContentEditor.preloadRemoteModule()

class Reply {
  // #
  // Creates a new reply to an Entry
  //
  // @param {view} an EntryView instance
  constructor(view, options = {}) {
    ;[
      'attachKeyboardShortcuts',
      'hide',
      'hideNotification',
      'submit',
      'onPostReplySuccess',
      'onPostReplyError'
    ].forEach(m => (this[m] = this[m].bind(this)))
    this.view = view
    this.options = options
    this.el = this.view.$('.discussion-reply-action:first')
    // works for threaded discussion topic and entries
    this.discussionEntry = this.el.closest('.discussion_entry')
    // required for non-threaded reply area at bottom of an entry block
    if (this.discussionEntry.length === 0) {
      this.discussionEntry = this.el.closest('.entry')
    }
    this.form = this.discussionEntry
      .find('form.discussion-reply-form:first')
      .submit(preventDefault(this.submit))
    this.textArea = this.getEditingElement()
    this.form.find('.cancel_button').click(this.hide)
    this.form.on('click', '.toggle-wrapper a', e => {
      e.preventDefault()
      RichContentEditor.callOnRCE(this.textArea, 'toggle')
      // hide the clicked link, and show the other toggle link.
      // todo: replace .andSelf with .addBack when JQuery is upgraded.
      return $(e.currentTarget)
        .siblings('a')
        .andSelf()
        .toggle()
    })
    this.form.delegate('.alert .close', 'click', preventDefault(this.hideNotification))
    this.editing = false

    _.defer(this.attachKeyboardShortcuts)
  }

  attachKeyboardShortcuts() {
    if (!ENV.use_rce_enhancements) {
      return this.view
        .$('.toggle-wrapper')
        .first()
        .before(new KeyboardShortcuts().render().$el)
    }
  }

  // #
  // Shows or hides the TinyMCE editor for a reply
  //
  // @api public
  toggle() {
    if (!this.editing) {
      return this.edit()
    } else {
      return this.hide()
    }
  }

  // #
  // Shows the TinyMCE editor for a reply
  //
  // @api public
  edit() {
    this.form.addClass('replying')
    this.discussionEntry.addClass('replying')
    RichContentEditor.initSidebar()
    RichContentEditor.loadNewEditor(this.textArea, {
      focus: true,
      manageParent: true,
      tinyOptions: {
        width: '100%'
      }
    })
    this.editing = true
    return this.trigger('edit', this)
  }

  createTextArea(id) {
    return $('<textarea/>')
      .addClass('reply-textarea')
      .attr('id', id)
      .attr('aria-hidden', 'true')
  }

  replaceTextArea(textAreaId) {
    RichContentEditor.destroyRCE(this.textArea)
    this.textArea = this.createTextArea(textAreaId)
    this.textArea.val(this.content)
    $(`#tinymce-parent-of-${textAreaId}`).replaceWith(this.textArea)
  }

  // #
  // Hides the TinyMCE editor
  //
  // @api public
  hide() {
    const textAreaId = this.textArea.attr('id')
    this.content = RichContentEditor.callOnRCE(this.textArea, 'get_code')
    this.form.removeClass('replying')
    this.discussionEntry.removeClass('replying')
    this.editing = false
    this.replaceTextArea(textAreaId)
    this.trigger('hide', this)
    return this.discussionEntry.find('.discussion-reply-action').focus()
  }

  hideNotification() {
    return this.view.model.set('notification', '')
  }

  // #
  // Submit handler for the reply form. Creates a new Entry and saves it
  // to the server.
  //
  // @api private
  submit() {
    this.hide()
    this.view.model.set(
      'notification',
      `<div class='alert alert-info'>${htmlEscape(I18n.t('saving_reply', 'Saving reply...'))}</div>`
    )
    const entry = new Entry(this.getModelAttributes())
    entry.save(null, {
      success: this.onPostReplySuccess,
      error: this.onPostReplyError,
      multipart: entry.get('attachment'),
      proxyAttachment: true
    })
    return this.removeAttachments()
  }

  // #
  // Get the jQueryEl element on the discussion entry to edit.
  //
  // @api private
  getEditingElement() {
    return this.view.$('.reply-textarea:first')
  }

  // #
  // Computes the model's attributes before saving it to the server
  //
  // @api private
  getModelAttributes() {
    const now = new Date().getTime()
    // TODO: remove this summary, server should send it in create response and no further
    // work is required
    return {
      summary: stripTags(this.content),
      message: this.content,
      parent_id: this.options.topLevel ? null : this.view.model.get('id'),
      user_id: ENV.current_user_id,
      created_at: now,
      updated_at: now,
      attachment: this.form.find('input[type=file]')[0],
      new: true
    }
  }

  // #
  // Callback when the model is succesfully saved
  //
  // @api private
  onPostReplySuccess(entry, response) {
    if (response.errors) {
      this.hideNotification()
      this.textArea.val(entry.get('message'))
      this.edit()
      return this.form.formErrors(response)
    } else {
      this.view.model.set('notification', '')
      this.trigger('save', entry)
      return this.textArea.val('')
    }
  }

  // #
  // Callback when the model fails to save
  //
  // @api private
  onPostReplyError(entry) {
    this.view.model.set(
      'notification',
      `
${I18n.t(
        '*An error occurred*, please post your reply again later',
        {
          wrapper: '$1'
        }
      )}
`
    )
    this.textArea.val(entry.get('message'))
    return this.edit()
  }

  // #
  // Adds an attachment
  addAttachment($el) {
    this.form.find('ul.discussion-reply-attachments').append(replyAttachmentTemplate())
    this.form.find('ul.discussion-reply-attachments input').focus()
    return this.form.find('a.discussion-reply-add-attachment').hide() // TODO: when the data model allows it, tweak this to support multiple in the UI
  }

  // #
  // Removes an attachment
  removeAttachment($el) {
    $el.closest('ul.discussion-reply-attachments li').remove()
    return this.form
      .find('a.discussion-reply-add-attachment')
      .show()
      .focus()
  }

  // #
  // Removes all attachments
  removeAttachments() {
    this.form.find('ul.discussion-reply-attachments').empty()
    return this.form
      .find('a.discussion-reply-add-attachment')
      .show()
      .focus()
  }
}

_.extend(Reply.prototype, Backbone.Events)

export default Reply
<commit_msg>show unfinished content warning in discussion replies<commit_after>//
// Copyright (C) 2012 - present Instructure, Inc.
//
// This file is part of Canvas.
//
// Canvas is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
// Software Foundation, version 3 of the License.
//
// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.

import Backbone from 'Backbone'
import _ from 'underscore'
import I18n from 'i18n!discussions.reply'
import $ from 'jquery'
import Entry from '../models/Entry'
import htmlEscape from 'str/htmlEscape'
import replyAttachmentTemplate from 'jst/discussions/_reply_attachment'
import preventDefault from '../fn/preventDefault'
import KeyboardShortcuts from '../views/editor/KeyboardShortcuts'
import stripTags from 'str/stripTags'
import RichContentEditor from 'jsx/shared/rce/RichContentEditor'
import 'jquery.instructure_forms'

RichContentEditor.preloadRemoteModule()

class Reply {
  // #
  // Creates a new reply to an Entry
  //
  // @param {view} an EntryView instance
  constructor(view, options = {}) {
    ;[
      'attachKeyboardShortcuts',
      'hide',
      'hideNotification',
      'submit',
      'onPostReplySuccess',
      'onPostReplyError'
    ].forEach(m => (this[m] = this[m].bind(this)))
    this.view = view
    this.options = options
    this.el = this.view.$('.discussion-reply-action:first')
    // works for threaded discussion topic and entries
    this.discussionEntry = this.el.closest('.discussion_entry')
    // required for non-threaded reply area at bottom of an entry block
    if (this.discussionEntry.length === 0) {
      this.discussionEntry = this.el.closest('.entry')
    }
    this.form = this.discussionEntry
      .find('form.discussion-reply-form:first')
      .submit(preventDefault(this.submit))
    this.textArea = this.getEditingElement()
    this.form.find('.cancel_button').click(this.hide)
    this.form.on('click', '.toggle-wrapper a', e => {
      e.preventDefault()
      RichContentEditor.callOnRCE(this.textArea, 'toggle')
      // hide the clicked link, and show the other toggle link.
      // todo: replace .andSelf with .addBack when JQuery is upgraded.
      return $(e.currentTarget)
        .siblings('a')
        .andSelf()
        .toggle()
    })
    this.form.delegate('.alert .close', 'click', preventDefault(this.hideNotification))
    this.editing = false

    _.defer(this.attachKeyboardShortcuts)
  }

  attachKeyboardShortcuts() {
    if (!ENV.use_rce_enhancements) {
      return this.view
        .$('.toggle-wrapper')
        .first()
        .before(new KeyboardShortcuts().render().$el)
    }
  }

  // #
  // Shows or hides the TinyMCE editor for a reply
  //
  // @api public
  toggle() {
    if (!this.editing) {
      return this.edit()
    } else {
      return this.hide()
    }
  }

  // #
  // Shows the TinyMCE editor for a reply
  //
  // @api public
  edit() {
    this.form.addClass('replying')
    this.discussionEntry.addClass('replying')
    RichContentEditor.initSidebar()
    RichContentEditor.loadNewEditor(this.textArea, {
      focus: true,
      manageParent: true,
      tinyOptions: {
        width: '100%'
      }
    })
    this.editing = true
    return this.trigger('edit', this)
  }

  createTextArea(id) {
    return $('<textarea/>')
      .addClass('reply-textarea')
      .attr('id', id)
      .attr('aria-hidden', 'true')
  }

  replaceTextArea(textAreaId) {
    RichContentEditor.destroyRCE(this.textArea)
    this.textArea = this.createTextArea(textAreaId)
    this.textArea.val(this.content)
    $(`#tinymce-parent-of-${textAreaId}`).replaceWith(this.textArea)
  }

  // #
  // Hides the TinyMCE editor
  //
  // @api public
  hide() {
    const textAreaId = this.textArea.attr('id')
    this.content = RichContentEditor.callOnRCE(this.textArea, 'get_code')
    this.form.removeClass('replying')
    this.discussionEntry.removeClass('replying')
    this.editing = false
    this.replaceTextArea(textAreaId)
    this.trigger('hide', this)
    return this.discussionEntry.find('.discussion-reply-action').focus()
  }

  hideNotification() {
    return this.view.model.set('notification', '')
  }

  // #
  // Submit handler for the reply form. Creates a new Entry and saves it
  // to the server.
  //
  // @api private
  submit() {
    const iframes = Array.from(document.querySelectorAll('iframe'))
    const incompleteIframe = iframes.find(item => {
      return item.contentWindow.document.body.querySelectorAll('[data-placeholder-for]').length > 0
    })

    if (incompleteIframe) {
      // confirm is also used in other RCE places like AssignmentEdit, and Root Discussions
      if (
        !window.confirm(
          I18n.t(
            'Content is still being uploaded, if you continue it will not be embedded properly.'
          )
        )
      ) {
        return
      }
    }

    this.hide()
    this.view.model.set(
      'notification',
      `<div class='alert alert-info'>${htmlEscape(I18n.t('saving_reply', 'Saving reply...'))}</div>`
    )
    const entry = new Entry(this.getModelAttributes())
    entry.save(null, {
      success: this.onPostReplySuccess,
      error: this.onPostReplyError,
      multipart: entry.get('attachment'),
      proxyAttachment: true
    })
    return this.removeAttachments()
  }

  // #
  // Get the jQueryEl element on the discussion entry to edit.
  //
  // @api private
  getEditingElement() {
    return this.view.$('.reply-textarea:first')
  }

  // #
  // Computes the model's attributes before saving it to the server
  //
  // @api private
  getModelAttributes() {
    const now = new Date().getTime()
    // TODO: remove this summary, server should send it in create response and no further
    // work is required
    return {
      summary: stripTags(this.content),
      message: this.content,
      parent_id: this.options.topLevel ? null : this.view.model.get('id'),
      user_id: ENV.current_user_id,
      created_at: now,
      updated_at: now,
      attachment: this.form.find('input[type=file]')[0],
      new: true
    }
  }

  // #
  // Callback when the model is succesfully saved
  //
  // @api private
  onPostReplySuccess(entry, response) {
    if (response.errors) {
      this.hideNotification()
      this.textArea.val(entry.get('message'))
      this.edit()
      return this.form.formErrors(response)
    } else {
      this.view.model.set('notification', '')
      this.trigger('save', entry)
      return this.textArea.val('')
    }
  }

  // #
  // Callback when the model fails to save
  //
  // @api private
  onPostReplyError(entry) {
    this.view.model.set(
      'notification',
      `
${I18n.t(
        '*An error occurred*, please post your reply again later',
        {
          wrapper: '$1'
        }
      )}
`
    )
    this.textArea.val(entry.get('message'))
    return this.edit()
  }

  // #
  // Adds an attachment
  addAttachment($el) {
    this.form.find('ul.discussion-reply-attachments').append(replyAttachmentTemplate())
    this.form.find('ul.discussion-reply-attachments input').focus()
    return this.form.find('a.discussion-reply-add-attachment').hide() // TODO: when the data model allows it, tweak this to support multiple in the UI
  }

  // #
  // Removes an attachment
  removeAttachment($el) {
    $el.closest('ul.discussion-reply-attachments li').remove()
    return this.form
      .find('a.discussion-reply-add-attachment')
      .show()
      .focus()
  }

  // #
  // Removes all attachments
  removeAttachments() {
    this.form.find('ul.discussion-reply-attachments').empty()
    return this.form
      .find('a.discussion-reply-add-attachment')
      .show()
      .focus()
  }
}

_.extend(Reply.prototype, Backbone.Events)

export default Reply
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Component.extend({
    showSelect: false,
    noFileFound: true,
    didRender() {
        if(!this.get('layer.settings.values.downloadLink')){
            this.get('node.files').then((result)=>{
                result.objectAt(0).get('files').then((files)=>{
                    if(files.length === 0){
                        this.set('noFileFound', true);
                        return false;
                    }else{
                        this.set('noFileFound', false);
                    }
                    let fileDatesLinks = {}
                    let fileModifiedDates = []
                    for(let i = 0; i < files.length; i++){
                        fileModifiedDates.push(files.objectAt(i).get('dateModified'));
                        fileDatesLinks[files.objectAt(i).get('dateModified')] = files.objectAt(i).get('links').download;
                    }
                    let mostRecentDate = new Date(Math.max.apply(null,fileModifiedDates));
                    this.set('layer.settings.values.downloadLink' , fileDatesLinks[mostRecentDate]);
                });
            });
        }
    },
    actions: {
        fileDetail(file) {
            this.set('showSelect', false);
            this.set('layer.settings.values.downloadLink' ,  file.data.links.download)
        },
        showSelect(){
            this.set('showSelect', true);
        },
        hideSelect(){
            this.set('showSelect', false);
        }
	}
});
<commit_msg>Fix 'No Files Found" showing when files are found<commit_after>import Ember from 'ember';

export default Ember.Component.extend({
    showSelect: false,
    noFileFound: true, //make computed property that observers the layers.[] and if change see if there is a file
    didRender() {

        console.log(this.get('layer.settings.values.downloadLink') ,  this.get('noFileFound'))
        if(!this.get('layer.settings.values.downloadLink')){
            this.get('node.files').then((result)=>{
                result.objectAt(0).get('files').then((files)=>{
                    console.log("files.length" , files.length);
                    if(files.length === 0){
                        this.set('noFileFound', true);
                        return false;
                    }else{
                        this.set('noFileFound', false);
                    }
                    let fileDatesLinks = {}
                    let fileModifiedDates = []
                    for(let i = 0; i < files.length; i++){
                        fileModifiedDates.push(files.objectAt(i).get('dateModified'));
                        fileDatesLinks[files.objectAt(i).get('dateModified')] = files.objectAt(i).get('links').download;
                    }
                    let mostRecentDate = new Date(Math.max.apply(null,fileModifiedDates));
                    this.set('layer.settings.values.downloadLink' , fileDatesLinks[mostRecentDate]);
                });
            });
        }else{
            this.set('noFileFound', false);
        }
    },
    actions: {
        fileDetail(file) {
            this.set('showSelect', false);
            this.set('layer.settings.values.downloadLink' ,  file.data.links.download)
        },
        showSelect(){
            this.set('showSelect', true);
        },
        hideSelect(){
            this.set('showSelect', false);
        }
    }
});
<|endoftext|>


<commit_before>// see https://github.com/chejen/eslint-plugin-i18n and https://github.com/Automattic/eslint-plugin-wpcalypso

module.exports = {
	// Disallow using three dots in translate strings
	'wpcalypso/i18n-ellipsis': 'warn',
	// Ensure placeholder counts match between singular and plural strings
	'wpcalypso/i18n-mismatched-placeholders': 'warn',
	// Disallow multiple unnamed placeholders
	'wpcalypso/i18n-named-placeholders': 'warn',
	// Disallow collapsible whitespace in translatable strings
	'wpcalypso/i18n-no-collapsible-whitespace': 'warn',
	// Disallow strings which include only placeholders
	'wpcalypso/i18n-no-placeholders-only': 'warn',
	// Disallow this.translate()
	'wpcalypso/i18n-no-this-translate': 'warn',
	// Disallow variables as translate strings
	'wpcalypso/i18n-no-variables': 'warn',
	// Enforce external, internal dependencies docblocks
	'wpcalypso/import-docblock': 'warn',
	// Disallow combineReducers import from redux
	'import-no-redux-combine-reducers': 'warn',
	// Ensure JSX className adheres to CSS namespace guidelines
	'wpcalypso/jsx-classname-namespace': 'warn',
	// Enforce recommended Gridicon size attributes
	'wpcalypso/jsx-gridicon-size': 'warn',
	// Disallow using the wildcard '*' in postMessage
	'wpcalypso/post-message-no-wildcard-targets': 'warn',
	// Disallow creation of selectors bound to Redux state
	'redux-no-bound-selectors': 'warn',
};
<commit_msg>fix: add missing `wpcalypso` namespace to `eslint-plugin-wpcalypso` rules<commit_after>// see https://github.com/chejen/eslint-plugin-i18n and https://github.com/Automattic/eslint-plugin-wpcalypso

module.exports = {
	// Disallow using three dots in translate strings
	'wpcalypso/i18n-ellipsis': 'warn',
	// Ensure placeholder counts match between singular and plural strings
	'wpcalypso/i18n-mismatched-placeholders': 'warn',
	// Disallow multiple unnamed placeholders
	'wpcalypso/i18n-named-placeholders': 'warn',
	// Disallow collapsible whitespace in translatable strings
	'wpcalypso/i18n-no-collapsible-whitespace': 'warn',
	// Disallow strings which include only placeholders
	'wpcalypso/i18n-no-placeholders-only': 'warn',
	// Disallow this.translate()
	'wpcalypso/i18n-no-this-translate': 'warn',
	// Disallow variables as translate strings
	'wpcalypso/i18n-no-variables': 'warn',
	// Enforce external, internal dependencies docblocks
	'wpcalypso/import-docblock': 'warn',
	// Disallow combineReducers import from redux
	'wpcalypso/import-no-redux-combine-reducers': 'warn',
	// Ensure JSX className adheres to CSS namespace guidelines
	'wpcalypso/jsx-classname-namespace': 'warn',
	// Enforce recommended Gridicon size attributes
	'wpcalypso/jsx-gridicon-size': 'warn',
	// Disallow using the wildcard '*' in postMessage
	'wpcalypso/post-message-no-wildcard-targets': 'warn',
	// Disallow creation of selectors bound to Redux state
	'wpcalypso/redux-no-bound-selectors': 'warn',
};
<|endoftext|>


<commit_before>const ApiaryReporter = require('./reporters/ApiaryReporter');
const BaseReporter = require('./reporters/BaseReporter');
const CLIReporter = require('./reporters/CLIReporter');
const DotReporter = require('./reporters/DotReporter');
const HTMLReporter = require('./reporters/HTMLReporter');
const MarkdownReporter = require('./reporters/MarkdownReporter');
const NyanCatReporter = require('./reporters/NyanReporter');
const XUnitReporter = require('./reporters/XUnitReporter');

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

const fileReporters = [
  'xunit',
  'html',
  'markdown',
  'apiary',
  'junit', // Deprecated
];

const cliReporters = ['dot', 'nyan'];

function intersection(a, b) {
  if (a.length > b.length) { [a, b] = Array.from([b, a]); }
  return Array.from(a).filter(value => Array.from(b).includes(value));
}

function configureReporters(config, stats, tests, runner) {
  addReporter('base', config.emitter, stats, tests);

  const reporters = config.options.reporter;
  const outputs = config.options.output;

  logger.verbose('Configuring reporters:', reporters, outputs);

  function addCli(reportersArr) {
    if (reportersArr.length > 0) {
      const usedCliReporters = intersection(reportersArr, cliReporters);
      if (usedCliReporters.length === 0) {
        return new CLIReporter(
          config.emitter, stats, tests, config.options['inline-errors'], config.options.details
        );
      }
      return addReporter(usedCliReporters[0], config.emitter, stats, tests);
    }
    return new CLIReporter(
      config.emitter, stats, tests, config.options['inline-errors'], config.options.details
    );
  }

  function addReporter(reporter, emitter, statistics, testsArg, path) {
    switch (reporter) {
      case 'xunit':
        return new XUnitReporter(emitter, statistics, testsArg, path, config.options.details);
      case 'junit': // Deprecated
        logger.warn('junit will be deprecated in the future. Please use `xunit` instead.');
        return new XUnitReporter(emitter, statistics, testsArg, path, config.options.details);
      case 'dot':
        return new DotReporter(emitter, statistics, testsArg);
      case 'nyan':
        return new NyanCatReporter(emitter, statistics, testsArg);
      case 'html':
        return new HTMLReporter(emitter, statistics, testsArg, path, config.options.details);
      case 'markdown':
        return new MarkdownReporter(emitter, statistics, testsArg, path, config.options.details);
      case 'apiary':
        return new ApiaryReporter(emitter, statistics, testsArg, config, runner);
      default:
        // I don't even know where to begin...
        // TODO: DESIGN / REFACTOR WHOLE REPORTER(S) API FROM SCRATCH, THIS IS MADNESS!!1
        (new BaseReporter(emitter, stats, tests));
    }
  }

  if (!config.options.silent) { addCli(reporters); }

  const usedFileReporters = intersection(reporters, fileReporters);

  stats.fileBasedReporters = usedFileReporters.length;

  if (usedFileReporters.length > 0) {
    let usedFileReportersLength = usedFileReporters.length;
    if (reporters.indexOf('apiary') > -1) {
      usedFileReportersLength -= 1;
    }

    if (usedFileReportersLength > outputs.length) {
      logger.warn(`
There are more reporters requiring output paths than there are output paths
provided. Using default paths for additional file-based reporters.
`);
    }

    return usedFileReporters.map((usedFileReporter, index) => {
      const path = outputs[index] ? outputs[index] : null;
      return addReporter(usedFileReporter, config.emitter, stats, tests, path);
    });
  }
}

module.exports = configureReporters;
<commit_msg>refactor: remove support for 'junit' reporter<commit_after>const ApiaryReporter = require('./reporters/ApiaryReporter');
const BaseReporter = require('./reporters/BaseReporter');
const CLIReporter = require('./reporters/CLIReporter');
const DotReporter = require('./reporters/DotReporter');
const HTMLReporter = require('./reporters/HTMLReporter');
const MarkdownReporter = require('./reporters/MarkdownReporter');
const NyanCatReporter = require('./reporters/NyanReporter');
const XUnitReporter = require('./reporters/XUnitReporter');

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

const fileReporters = [
  'xunit',
  'html',
  'markdown',
  'apiary',
];

const cliReporters = ['dot', 'nyan'];

function intersection(a, b) {
  if (a.length > b.length) { [a, b] = Array.from([b, a]); }
  return Array.from(a).filter(value => Array.from(b).includes(value));
}

function configureReporters(config, stats, tests, runner) {
  addReporter('base', config.emitter, stats, tests);

  const reporters = config.options.reporter;
  const outputs = config.options.output;

  logger.verbose('Configuring reporters:', reporters, outputs);

  function addCli(reportersArr) {
    if (reportersArr.length > 0) {
      const usedCliReporters = intersection(reportersArr, cliReporters);
      if (usedCliReporters.length === 0) {
        return new CLIReporter(
          config.emitter, stats, tests, config.options['inline-errors'], config.options.details
        );
      }
      return addReporter(usedCliReporters[0], config.emitter, stats, tests);
    }
    return new CLIReporter(
      config.emitter, stats, tests, config.options['inline-errors'], config.options.details
    );
  }

  function addReporter(reporter, emitter, statistics, testsArg, path) {
    switch (reporter) {
      case 'xunit':
        return new XUnitReporter(emitter, statistics, testsArg, path, config.options.details);
      case 'dot':
        return new DotReporter(emitter, statistics, testsArg);
      case 'nyan':
        return new NyanCatReporter(emitter, statistics, testsArg);
      case 'html':
        return new HTMLReporter(emitter, statistics, testsArg, path, config.options.details);
      case 'markdown':
        return new MarkdownReporter(emitter, statistics, testsArg, path, config.options.details);
      case 'apiary':
        return new ApiaryReporter(emitter, statistics, testsArg, config, runner);
      default:
        // I don't even know where to begin...
        // TODO: DESIGN / REFACTOR WHOLE REPORTER(S) API FROM SCRATCH, THIS IS MADNESS!!1
        (new BaseReporter(emitter, stats, tests));
    }
  }

  if (!config.options.silent) { addCli(reporters); }

  const usedFileReporters = intersection(reporters, fileReporters);

  stats.fileBasedReporters = usedFileReporters.length;

  if (usedFileReporters.length > 0) {
    let usedFileReportersLength = usedFileReporters.length;
    if (reporters.indexOf('apiary') > -1) {
      usedFileReportersLength -= 1;
    }

    if (usedFileReportersLength > outputs.length) {
      logger.warn(`
There are more reporters requiring output paths than there are output paths
provided. Using default paths for additional file-based reporters.
`);
    }

    return usedFileReporters.map((usedFileReporter, index) => {
      const path = outputs[index] ? outputs[index] : null;
      return addReporter(usedFileReporter, config.emitter, stats, tests, path);
    });
  }
}

module.exports = configureReporters;
<|endoftext|>


<commit_before>'use strict';
const net = require('net');
const tls = require('tls');
const Connection = require('./connection');

function connect(options, callback) {
  if (options.family !== void 0) {
    makeConnection(options.family, options, (err, socket) => {
      if (err) {
        return callback(err, socket); // in the error case, `socket` is the originating error event
      }

      callback(null, new Connection(socket, options));
    });

    return;
  }

  return makeConnection(6, options, (err, ipv6Socket) => {
    if (err) {
      // if (this.logger.isDebug()) {
      //   this.logger.debug(
      //     `connection ${this.id} for [${this.address}] errored out with [${JSON.stringify(err)}]`
      //   );
      // }

      makeConnection(4, options, (err, ipv4Socket) => {
        if (err) {
          return callback(err, ipv4Socket); // in the error case, `ipv4Socket` is the originating error event
        }

        callback(null, new Connection(ipv4Socket, options));
      });

      return;
    }

    callback(null, new Connection(ipv6Socket, options));
  });
}

const LEGAL_SSL_SOCKET_OPTIONS = [
  'pfx',
  'key',
  'passphrase',
  'cert',
  'ca',
  'ciphers',
  'NPNProtocols',
  'ALPNProtocols',
  'servername',
  'ecdhCurve',
  'secureProtocol',
  'secureContext',
  'session',
  'minDHSize',
  'crl'
];

function parseConnectOptions(family, options) {
  const host = typeof options.host === 'string' ? options.host : 'localhost';
  if (host.indexOf('/') !== -1) {
    return { path: host };
  }

  const result = {
    family,
    host,
    port: typeof options.port === 'number' ? options.port : 27017,
    rejectUnauthorized: false
  };

  return result;
}

function parseSslOptions(family, options) {
  const result = parseConnectOptions(family, options);

  // Merge in valid SSL options
  for (const name in options) {
    if (options[name] != null && LEGAL_SSL_SOCKET_OPTIONS.indexOf(name) !== -1) {
      result[name] = options[name];
    }
  }

  // Override checkServerIdentity behavior
  if (options.checkServerIdentity === false) {
    // Skip the identiy check by retuning undefined as per node documents
    // https://nodejs.org/api/tls.html#tls_tls_connect_options_callback
    result.checkServerIdentity = function() {
      return undefined;
    };
  } else if (typeof options.checkServerIdentity === 'function') {
    result.checkServerIdentity = options.checkServerIdentity;
  }

  // Set default sni servername to be the same as host
  if (result.servername == null) {
    result.servername = result.host;
  }

  if (typeof options.rejectUnauthorized === 'boolean') {
    result.rejectUnauthorized = options.rejectUnauthorized;
  }

  return result;
}

function makeConnection(family, options, callback) {
  const useSsl = typeof options.ssl === 'boolean' ? options.ssl : false;
  const keepAlive = typeof options.keepAlive === 'boolean' ? options.keepAlive : true;
  let keepAliveInitialDelay =
    typeof options.keepAliveInitialDelay === 'number' ? options.keepAliveInitialDelay : 300000;
  const noDelay = typeof options.noDelay === 'boolean' ? options.noDelay : true;
  const connectionTimeout =
    typeof options.connectionTimeout === 'number' ? options.connectionTimeout : 30000;
  const socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 360000;
  const rejectUnauthorized =
    typeof options.rejectUnauthorized === 'boolean' ? options.rejectUnauthorized : true;

  if (keepAliveInitialDelay > socketTimeout) {
    keepAliveInitialDelay = Math.round(socketTimeout / 2);
  }

  let socket;
  try {
    if (useSsl) {
      socket = tls.connect(parseSslOptions(family, options));
    } else {
      socket = net.createConnection(parseConnectOptions(family, options));
    }
  } catch (err) {
    return callback(err);
  }

  socket.setKeepAlive(keepAlive, keepAliveInitialDelay);
  socket.setTimeout(connectionTimeout);
  socket.setNoDelay(noDelay);

  const errorEvents = ['error', 'close', 'timeout', 'parseError', 'connect'];
  function errorHandler(eventName) {
    return err => {
      if (err == null || err === false) err = true;
      errorEvents.forEach(event => socket.removeAllListeners(event));
      socket.removeListener('connect', connectHandler);
      callback(err, eventName);
    };
  }

  function connectHandler() {
    errorEvents.forEach(event => socket.removeAllListeners(event));
    if (socket.authorizationError && rejectUnauthorized) {
      return callback(socket.authorizationError);
    }

    socket.setTimeout(socketTimeout);
    callback(null, socket);
  }

  socket.once('error', errorHandler('error'));
  socket.once('close', errorHandler('close'));
  socket.once('timeout', errorHandler('timeout'));
  socket.once('parseError', errorHandler('parseError'));
  socket.once('connect', connectHandler);
}

module.exports = connect;
<commit_msg>refactor(connect): move `rejectUnauthorized` into LEGAL_SSL_OPTIONS<commit_after>'use strict';
const net = require('net');
const tls = require('tls');
const Connection = require('./connection');

function connect(options, callback) {
  if (options.family !== void 0) {
    makeConnection(options.family, options, (err, socket) => {
      if (err) {
        return callback(err, socket); // in the error case, `socket` is the originating error event name
      }

      callback(null, new Connection(socket, options));
    });

    return;
  }

  return makeConnection(6, options, (err, ipv6Socket) => {
    if (err) {
      makeConnection(4, options, (err, ipv4Socket) => {
        if (err) {
          return callback(err, ipv4Socket); // in the error case, `ipv4Socket` is the originating error event name
        }

        callback(null, new Connection(ipv4Socket, options));
      });

      return;
    }

    callback(null, new Connection(ipv6Socket, options));
  });
}

const LEGAL_SSL_SOCKET_OPTIONS = [
  'pfx',
  'key',
  'passphrase',
  'cert',
  'ca',
  'ciphers',
  'NPNProtocols',
  'ALPNProtocols',
  'servername',
  'ecdhCurve',
  'secureProtocol',
  'secureContext',
  'session',
  'minDHSize',
  'crl',
  'rejectUnauthorized'
];

function parseConnectOptions(family, options) {
  const host = typeof options.host === 'string' ? options.host : 'localhost';
  if (host.indexOf('/') !== -1) {
    return { path: host };
  }

  const result = {
    family,
    host,
    port: typeof options.port === 'number' ? options.port : 27017,
    rejectUnauthorized: false
  };

  return result;
}

function parseSslOptions(family, options) {
  const result = parseConnectOptions(family, options);

  // Merge in valid SSL options
  for (const name in options) {
    if (options[name] != null && LEGAL_SSL_SOCKET_OPTIONS.indexOf(name) !== -1) {
      result[name] = options[name];
    }
  }

  // Override checkServerIdentity behavior
  if (options.checkServerIdentity === false) {
    // Skip the identiy check by retuning undefined as per node documents
    // https://nodejs.org/api/tls.html#tls_tls_connect_options_callback
    result.checkServerIdentity = function() {
      return undefined;
    };
  } else if (typeof options.checkServerIdentity === 'function') {
    result.checkServerIdentity = options.checkServerIdentity;
  }

  // Set default sni servername to be the same as host
  if (result.servername == null) {
    result.servername = result.host;
  }

  return result;
}

function makeConnection(family, options, callback) {
  const useSsl = typeof options.ssl === 'boolean' ? options.ssl : false;
  const keepAlive = typeof options.keepAlive === 'boolean' ? options.keepAlive : true;
  let keepAliveInitialDelay =
    typeof options.keepAliveInitialDelay === 'number' ? options.keepAliveInitialDelay : 300000;
  const noDelay = typeof options.noDelay === 'boolean' ? options.noDelay : true;
  const connectionTimeout =
    typeof options.connectionTimeout === 'number' ? options.connectionTimeout : 30000;
  const socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 360000;
  const rejectUnauthorized =
    typeof options.rejectUnauthorized === 'boolean' ? options.rejectUnauthorized : true;

  if (keepAliveInitialDelay > socketTimeout) {
    keepAliveInitialDelay = Math.round(socketTimeout / 2);
  }

  let socket;
  try {
    if (useSsl) {
      socket = tls.connect(parseSslOptions(family, options));
    } else {
      socket = net.createConnection(parseConnectOptions(family, options));
    }
  } catch (err) {
    return callback(err);
  }

  socket.setKeepAlive(keepAlive, keepAliveInitialDelay);
  socket.setTimeout(connectionTimeout);
  socket.setNoDelay(noDelay);

  const errorEvents = ['error', 'close', 'timeout', 'parseError', 'connect'];
  function errorHandler(eventName) {
    return err => {
      if (err == null || err === false) err = true;
      errorEvents.forEach(event => socket.removeAllListeners(event));
      socket.removeListener('connect', connectHandler);
      callback(err, eventName);
    };
  }

  function connectHandler() {
    errorEvents.forEach(event => socket.removeAllListeners(event));
    if (socket.authorizationError && rejectUnauthorized) {
      return callback(socket.authorizationError);
    }

    socket.setTimeout(socketTimeout);
    callback(null, socket);
  }

  socket.once('error', errorHandler('error'));
  socket.once('close', errorHandler('close'));
  socket.once('timeout', errorHandler('timeout'));
  socket.once('parseError', errorHandler('parseError'));
  socket.once('connect', connectHandler);
}

module.exports = connect;
<|endoftext|>


<commit_before>// Imported from json.js
var elasticsearch = require('elasticsearch');
var querystring = require('querystring');
var cip = require('../cip-methods.js');
var helpers = {};

var page_size = 10;

helpers.catalog_url = function catalog_url(result) {
  return '/' + result.catalog;
};

helpers.asset_url = function asset_url(result) {
  return '/' + result.catalog + '/' + result.id;
};

helpers.thumbnail_url = function thumbnail_url(result) {
  return '/' + result.catalog + '/' + result.id + '/thumbnail';
};

helpers.find_catalog = function find_catalog(req, alias) {
  var catalog = cip.find_catalog(req.app.get('cip_catalogs'), alias);
  if(!catalog) {
      return null;
  }
  return catalog.name;
};

function search(es_client, params, freetext, offset, callback) {
  var query = {
    index: 'assets',
    size: page_size,
    from: offset,
    body: {}
  };

  query.body = {
      sort: [
          {'modification_time': 'desc'}
      ],
      filter: {
          and: []
      }
  };

  // Add catalog query parameter
  if (params.hasOwnProperty('catalog')) {
    query.body.filter.and.push({
      query: {match: {"catalog":params['catalog']}}
    });
  }

  // Add category query parameter
  if (params.hasOwnProperty('categories_int')) {
    query.body.filter.and.push({
      query: {match: {"categories_int":params['categories_int']} }
    });
  }

  if(freetext !== undefined && freetext.length > 0) {
    query.body.filter.and.push(
          {
            query: {
                query_string: {
                    query: "_all:\"" + freetext + "\""
            }
          }
      }
    );
  }

  if(query.body.filter.and.length === 0) {
      delete query.body.filter.and;
  }

  es_client.search(query).then(function (resp) {
    var results = [];
    var temp_results = resp.hits.hits;
    var total_results = resp.hits.total;

    for(var i=0; i < temp_results.length; ++i) {
      results.push(temp_results[i]._source);
    }
      callback(results, offset, total_results);
  });
}
// Import ends

// Front page
exports.index = function index(req, res) {
    var es_client = req.app.get('es_client');
    var freetext = req.param('q');

    // Get the intial assets
    search(es_client, {}, freetext, 0,
      function(results, offset, total_results) {
        var final_results = {
          offset: offset,
          total_results: total_results,
          results: []
        };

      var real_results = [];
      for(var i=0; i < results.length; ++i) {
        real_results.push({
          title: results[i].short_title,
          url: helpers.asset_url(results[i]),
          thumbnail_url: helpers.thumbnail_url(results[i]),
          catalog_url: helpers.catalog_url(results[i]),
          description: results[i].description,
          catalog: (helpers.find_catalog(req, results[i].catalog) || '')
        });
      }

      final_results.results = real_results;

      // Heading for either front page or search page
      if (freetext != undefined) {
        final_results.is_search = true;
      } else {
        final_results.is_front = true;
      }

      res.render('search', {
          req: req,
          infinitepath: freetext != undefined ? 'infinite/null/' + '2?q=' + freetext : 'infinite/null/' + '2', // Append query terms if present
          results: final_results,
          freetext: freetext
      });
    }
  );
};

// Catalog page
exports.catalog = function catalog(req, res) {
  var category = req.param('cat');
  var es_client = req.app.get('es_client');
  var offset = 0;
  var query_params = {};

  if(req.params.catalog != undefined) {
    query_params['catalog'] = req.params.catalog; // Grab the current catalog
  }

  if(category != undefined && category) {
    query_params['categories_int'] = category;
  }

  // Get the intial assets
  search(es_client, query_params, '', offset,
    function(results, offset, total_results) {
      var final_results = {
        offset: offset,
        total_results: total_results,
        results: []
      };

      var real_results = [];
      for(var i=0; i < results.length; ++i) {
          real_results.push({
            title: results[i].short_title,
            url: helpers.asset_url(results[i]),
            thumbnail_url: helpers.thumbnail_url(results[i]),
            catalog_url: helpers.catalog_url(results[i]),
            description: results[i].description,
            catalog: (helpers.find_catalog(req, results[i].catalog) || '')
        });
      }

      final_results.results = real_results;

      // Heading for either catalog, cat page - first grab some meta data
      var catalogs = req.app.get('cip_catalogs');
      var categories = req.app.get('cip_categories');
      var metadata = [];

      for(var i=0; i < catalogs.length; ++i) {
          if(catalogs[i].alias == undefined || catalogs[i].alias !== req.params.catalog)
              continue;

          var children = categories[catalogs[i].alias].tree;

          metadata.push({
              name: catalogs[i].name,
              alias: catalogs[i].alias,
              children: children
          });
      }
      metadata = metadata[0];

      if (req.params.catalog != undefined && category != undefined && category) {
        // Retrieve the breadcrumbs
        var bread = cip.nested_categories(metadata.children.children, category);
        metadata.breadcrumbs = bread;

        final_results.is_category = true;
      } else {
        final_results.is_catalog = true;
      }

      res.render('search', {
        req: req,
        infinitepath: 'infinite/' + req.param('catalog') + '/2?cat=' + category,
        results: final_results,
        metadata:metadata
      });
    }
  );
};

// Infinite scroll for both catalog pages, front page and search pages
exports.infinite = function(req, res) {
  var freetext = req.param('q');
  var category = req.param('cat');
  var offset = (req.params.id - 1 ) * page_size; // Offset by page size
  var es_client = req.app.get('es_client');
  var query_params = {};

  if(req.params.catalog != undefined && req.params.catalog != 'null') {
    query_params['catalog'] = req.params.catalog;
  }

  if(category != undefined && category != 'undefined' && category) {
    query_params['categories_int'] = category;
  }

  search(es_client,query_params, freetext, offset,
    function(results, offset, total_results) {
        var final_results = {
          offset: offset,
          total_results: total_results,
          results: []
        };

      var real_results = [];
      for(var i=0; i < results.length; ++i) {
          real_results.push({
            title: results[i].short_title,
            url: helpers.asset_url(results[i]),
            thumbnail_url: helpers.thumbnail_url(results[i]),
            catalog_url: helpers.catalog_url(results[i]),
            description: results[i].description,
            catalog: (helpers.find_catalog(req, results[i].catalog) || '')
        });
      }

      final_results.results = real_results;

      res.render('components/infinite-search', {
        req: req,
        infinitepath: 'infinite/' + req.param('catalog') + '/2',
        results: final_results.results
      });
    }
  );
};<commit_msg>Fixed a bug on scrolling, when user had searched<commit_after>// Imported from json.js
var elasticsearch = require('elasticsearch');
var querystring = require('querystring');
var cip = require('../cip-methods.js');
var helpers = {};

var page_size = 10;

helpers.catalog_url = function catalog_url(result) {
  return '/' + result.catalog;
};

helpers.asset_url = function asset_url(result) {
  return '/' + result.catalog + '/' + result.id;
};

helpers.thumbnail_url = function thumbnail_url(result) {
  return '/' + result.catalog + '/' + result.id + '/thumbnail';
};

helpers.find_catalog = function find_catalog(req, alias) {
  var catalog = cip.find_catalog(req.app.get('cip_catalogs'), alias);
  if(!catalog) {
      return null;
  }
  return catalog.name;
};

function search(es_client, params, freetext, offset, callback) {
  var query = {
    index: 'assets',
    size: page_size,
    from: offset,
    body: {}
  };

  query.body = {
      sort: [
          {'modification_time': 'desc'}
      ],
      filter: {
          and: []
      }
  };

  // Add catalog query parameter
  if (params.hasOwnProperty('catalog')) {
    query.body.filter.and.push({
      query: {match: {"catalog":params['catalog']}}
    });
  }

  // Add category query parameter
  if (params.hasOwnProperty('categories_int')) {
    query.body.filter.and.push({
      query: {match: {"categories_int":params['categories_int']} }
    });
  }

  if(freetext !== undefined && freetext.length > 0) {
    query.body.filter.and.push({
        query: {
            query_string: {
                query: "_all:\"" + freetext + "\""
            }
        }
    });
  }

  if(query.body.filter.and.length === 0) {
      delete query.body.filter.and;
  }

  es_client.search(query).then(function (resp) {
    var results = [];
    var temp_results = resp.hits.hits;
    var total_results = resp.hits.total;

    for(var i=0; i < temp_results.length; ++i) {
      results.push(temp_results[i]._source);
    }
      callback(results, offset, total_results);
  });
}
// Import ends

// Front page
exports.index = function index(req, res) {
    var es_client = req.app.get('es_client');
    var freetext = req.param('q');

    // Get the intial assets
    search(es_client, {}, freetext, 0,
      function(results, offset, total_results) {
        var final_results = {
          offset: offset,
          total_results: total_results,
          results: []
        };

      var real_results = [];
      for(var i=0; i < results.length; ++i) {
        real_results.push({
          title: results[i].short_title,
          url: helpers.asset_url(results[i]),
          thumbnail_url: helpers.thumbnail_url(results[i]),
          catalog_url: helpers.catalog_url(results[i]),
          description: results[i].description,
          catalog: (helpers.find_catalog(req, results[i].catalog) || '')
        });
      }

      final_results.results = real_results;

      // Heading for either front page or search page
      if (freetext != undefined) {
        final_results.is_search = true;
      } else {
        final_results.is_front = true;
      }

      res.render('search', {
          req: req,
          infinitepath: freetext != undefined ? 'infinite/null/' + '2?q=' + querystring.escape(freetext) : 'infinite/null/' + '2', // Append query terms if present
          results: final_results,
          freetext: freetext
      });
    }
  );
};

// Catalog page
exports.catalog = function catalog(req, res) {
  var category = req.param('cat');
  var es_client = req.app.get('es_client');
  var offset = 0;
  var query_params = {};

  if(req.params.catalog != undefined) {
    query_params['catalog'] = req.params.catalog; // Grab the current catalog
  }

  if(category != undefined && category) {
    query_params['categories_int'] = category;
  }

  // Get the intial assets
  search(es_client, query_params, '', offset,
    function(results, offset, total_results) {
      var final_results = {
        offset: offset,
        total_results: total_results,
        results: []
      };

      var real_results = [];
      for(var i=0; i < results.length; ++i) {
          real_results.push({
            title: results[i].short_title,
            url: helpers.asset_url(results[i]),
            thumbnail_url: helpers.thumbnail_url(results[i]),
            catalog_url: helpers.catalog_url(results[i]),
            description: results[i].description,
            catalog: (helpers.find_catalog(req, results[i].catalog) || '')
        });
      }

      final_results.results = real_results;

      // Heading for either catalog, cat page - first grab some meta data
      var catalogs = req.app.get('cip_catalogs');
      var categories = req.app.get('cip_categories');
      var metadata = [];

      for(var i=0; i < catalogs.length; ++i) {
          if(catalogs[i].alias == undefined || catalogs[i].alias !== req.params.catalog)
              continue;

          var children = categories[catalogs[i].alias].tree;

          metadata.push({
              name: catalogs[i].name,
              alias: catalogs[i].alias,
              children: children
          });
      }
      metadata = metadata[0];

      if (req.params.catalog != undefined && category != undefined && category) {
        // Retrieve the breadcrumbs
        var bread = cip.nested_categories(metadata.children.children, category);
        metadata.breadcrumbs = bread;

        final_results.is_category = true;
      } else {
        final_results.is_catalog = true;
      }

      res.render('search', {
        req: req,
        infinitepath: 'infinite/' + req.param('catalog') + '/2?cat=' + category,
        results: final_results,
        metadata:metadata
      });
    }
  );
};

// Infinite scroll for both catalog pages, front page and search pages
exports.infinite = function(req, res) {
  var freetext = req.param('q');
  var category = req.param('cat');
  var offset = (req.params.id - 1 ) * page_size; // Offset by page size
  var es_client = req.app.get('es_client');
  var query_params = {};

  if(req.params.catalog != undefined && req.params.catalog != 'null') {
    query_params['catalog'] = req.params.catalog;
  }

  if(category != undefined && category != 'undefined' && category) {
    query_params['categories_int'] = category;
  }

  search(es_client,query_params, freetext, offset,
    function(results, offset, total_results) {
        var final_results = {
          offset: offset,
          total_results: total_results,
          results: []
        };

      var real_results = [];
      for(var i=0; i < results.length; ++i) {
          real_results.push({
            title: results[i].short_title,
            url: helpers.asset_url(results[i]),
            thumbnail_url: helpers.thumbnail_url(results[i]),
            catalog_url: helpers.catalog_url(results[i]),
            description: results[i].description,
            catalog: (helpers.find_catalog(req, results[i].catalog) || '')
        });
      }

      final_results.results = real_results;

      res.render('components/infinite-search', {
        req: req,
        infinitepath: 'infinite/' + req.param('catalog') + '/2',
        results: final_results.results
      });
    }
  );
};
<|endoftext|>


<commit_before>// Dependencies
var async = require('async');
var _ = require('underscore');
_.str = require('underscore.string');
var parley = require('parley');

// Prototype definitions
var Adapter = require('./adapter.js');
var Collection = require('./collection.js');

// Util
var modules = require('sails-moduleloader');
var util = require('sails-util');
var fs = require('fs-extra');

/**
* Prepare waterline to interact with adapters
*/
module.exports = function (options,cb) {
	var self = this;

	// Read default global waterline config and extend with user options
	var waterlineConfig = require('./config.js');
	var collectionDefaults = _.extend(waterlineConfig.collection,options.collection);
	waterlineConfig = _.extend(waterlineConfig, options);
	waterlineConfig.collection = collectionDefaults;

	// Trim slashes off of app path
	waterlineConfig.appPath = _.str.rtrim(waterlineConfig.appPath,'/');
	console.log("APP PATH",waterlineConfig.appPath);

	// Only tear down waterline once
	// (if teardown() is called explicitly, don't tear it down when the process exits)
	var tornDown = false;

	// If logger was passed in, use it.  Otherwise, use the console.
	var log = options.log || console.log;

	var adapterDefs = _.extend({},options.adapters);

	// Create dictionaries out of adapter and collection defs
	var collectionDefs = _.extend({},options.collections);

	// Search collections for adapters not passed in and require them
	// Then merge that set with the passed-in adapters
	_.extend(adapterDefs,getMissingAdapters(collectionDefs));

	// Spawn asynchronous thread of execution
	var $$ = new parley();

	// Instantiate adapters
	var adapters = _.clone(adapterDefs);
	$$(async.forEach)(_.keys(adapters), instantiateAdapter);

	// Extend collection defs using their adapter defaults,
	// then waterline defaults
	$$(function (xcb) {
		collectionDefs = util.objMap(collectionDefs, function (collectionDef) {
			return _.defaults(collectionDef, waterlineConfig.collection, adapters[collectionDef.adapter].defaults);
		});
		xcb();
	})();

	// Set up transaction collections for each adapter, if they have a commitLog defined
	$$(async.forEach)(_.keys(adapters), function (adapterName, cb) {
		var commitLog = adapters[adapterName].commitLog;
		if (!commitLog) return cb();

		var transactionAdapter = adapters[commitLog.adapter];

		// If transaction adapter unknown, fetch it
		if (!transactionAdapter) {
			adapters[commitLog.adapter] = requireAdapter(commitLog.adapter);
			instantiateAdapter(commitLog.adapter, afterwards);
		}
		else afterwards(null, transactionAdapter);

		function afterwards(err, transactionAdapter) {
			if (err) return cb(err);

			// Instantiate transaction collection
			new Collection(commitLog, transactionAdapter, function (err,transactionCollection) {
				adapters[adapterName].transactionCollection = transactionCollection;
				return cb();
			});
		}
	});

	// Instantiate collections
	var collections = _.clone(collectionDefs);
	$$(async.forEach)(_.keys(collections), instantiateCollection);

	// Provide hook for process-end event and teardown waterline
	$$(listenForProcessEnd)();

	// Waterline is ready-- fire the callback,
	// passing back instantiated adapters and models
	$$(cb)(null, {
		adapters: adapters,
		collections: collections
	});

	// Discover and include any "missing" adapters
	// (adapters which are defined in collections but were not passed in)
	function getMissingAdapters (collectionDefs) {
		var foundAdapterDefs = {};

		_.each(collectionDefs, function (collectionDef, collectionName) {

			// Assume default adapter if adapter is not defined
			if (!collectionDef.adapter) collectionDef.adapter = waterlineConfig.collection.adapter;

			var adapterName = collectionDef.adapter;

			// If the adapter def is not a string, something is not right
			if (!_.isString(adapterName)) throw new Error("Invalid adapter name ("+adapterName+") in collection (" + collectionName +")");

			// Already found
			else if (foundAdapterDefs[adapterName]) return;

			// User adapter defined-- use that
			else if (adapterDefs[adapterName]) foundAdapterDefs[adapterName] = adapterDefs[adapterName]();

			// If this adapter is unknown, try to require it
			else foundAdapterDefs[adapterName] = requireAdapter(adapterName, collectionName);
		});

		return foundAdapterDefs;
	}

	// Try to import an unknown adapter
	function requireAdapter(adapterName, collectionName) {

		// First, try to stat the adapter module
		var modulePath = waterlineConfig.appPath+'/node_modules/'+adapterName;
		var exists = fs.existsSync(modulePath);
		log.verbose('Loading module ' + adapterName + "...");

		// If it exists, require it.
		if (exists) return require(modulePath)();
		else {
			var err = "Unknown adapter ("+adapterName+") in collection (" + collectionName +")";
			log.error(err);
			log.error("Try running: npm install "+adapterName);
			log.error("To save the adapter as a permanent dependency, run: npm install "+adapterName+" --save");
			process.exit(1);
		}

	}

	// Adapter definition must be called as a function
	// Defining adapters as functions allows them maximum flexibility (vs. simple objects)
	function instantiateAdapter (adapterName, cb) {
		new Adapter(adapters[adapterName], function (err, adapter) {
			adapters[adapterName] = adapter;
			return cb(err, adapter);
		});
	}

	// Instantiate a new collection
	function instantiateCollection (collectionName, cb) {
		var collectionDef = collections[collectionName];

		// Get instantiated adapter for this collection
		var adapter = adapters[collectionDef.adapter];

		// Instantiate new collection using definition and adapter
		new Collection(collectionDef, adapter, function(err, collection) {
			collections[collectionName] = collection;
			return cb(err);
		});
	}

	// Assign listener for process-end
	// (this logic lives here to avoid assigning multiple events in each adapter and collection)
	function listenForProcessEnd(xcb) {
		var exiting = false;
		process.on('SIGINT', serverOnHalt);
		process.on('SIGTERM', serverOnHalt);
		// If SIGINT or SIGTERM wasn't run, this is a dirty exit--
		process.on('exit', function process_exit() {
			if (!exiting) log.warn("Server stopped itself.");
		});
		function serverOnHalt (){
			exiting = true;
			teardown(function () {
				process.exit();
			});
		}
		xcb();
	}

	// Tear down all open waterline adapters (and therefore, collections)
	function teardown (cb) {
		cb = cb || function(){};

		// Only tear down once
		if (tornDown) return cb("Waterline is already torn down!  I can't do it again!");
		tornDown = true;

		// Fire each adapter's teardown event
		async.forEach(_.values(adapters),function (adapter,cb) {
			adapter.teardown(cb);
		},cb);
	}

	// Expose public teardown() method
	module.exports.teardown = teardown;

};<commit_msg>Removed log.<commit_after>// Dependencies
var async = require('async');
var _ = require('underscore');
_.str = require('underscore.string');
var parley = require('parley');

// Prototype definitions
var Adapter = require('./adapter.js');
var Collection = require('./collection.js');

// Util
var modules = require('sails-moduleloader');
var util = require('sails-util');
var fs = require('fs-extra');

/**
* Prepare waterline to interact with adapters
*/
module.exports = function (options,cb) {
	var self = this;

	// Read default global waterline config and extend with user options
	var waterlineConfig = require('./config.js');
	var collectionDefaults = _.extend(waterlineConfig.collection,options.collection);
	waterlineConfig = _.extend(waterlineConfig, options);
	waterlineConfig.collection = collectionDefaults;

	// Trim slashes off of app path
	waterlineConfig.appPath = _.str.rtrim(waterlineConfig.appPath,'/');

	// Only tear down waterline once
	// (if teardown() is called explicitly, don't tear it down when the process exits)
	var tornDown = false;

	// If logger was passed in, use it.  Otherwise, use the console.
	var log = options.log || console.log;

	var adapterDefs = _.extend({},options.adapters);

	// Create dictionaries out of adapter and collection defs
	var collectionDefs = _.extend({},options.collections);

	// Search collections for adapters not passed in and require them
	// Then merge that set with the passed-in adapters
	_.extend(adapterDefs,getMissingAdapters(collectionDefs));

	// Spawn asynchronous thread of execution
	var $$ = new parley();

	// Instantiate adapters
	var adapters = _.clone(adapterDefs);
	$$(async.forEach)(_.keys(adapters), instantiateAdapter);

	// Extend collection defs using their adapter defaults,
	// then waterline defaults
	$$(function (xcb) {
		collectionDefs = util.objMap(collectionDefs, function (collectionDef) {
			return _.defaults(collectionDef, waterlineConfig.collection, adapters[collectionDef.adapter].defaults);
		});
		xcb();
	})();

	// Set up transaction collections for each adapter, if they have a commitLog defined
	$$(async.forEach)(_.keys(adapters), function (adapterName, cb) {
		var commitLog = adapters[adapterName].commitLog;
		if (!commitLog) return cb();

		var transactionAdapter = adapters[commitLog.adapter];

		// If transaction adapter unknown, fetch it
		if (!transactionAdapter) {
			adapters[commitLog.adapter] = requireAdapter(commitLog.adapter);
			instantiateAdapter(commitLog.adapter, afterwards);
		}
		else afterwards(null, transactionAdapter);

		function afterwards(err, transactionAdapter) {
			if (err) return cb(err);

			// Instantiate transaction collection
			new Collection(commitLog, transactionAdapter, function (err,transactionCollection) {
				adapters[adapterName].transactionCollection = transactionCollection;
				return cb();
			});
		}
	});

	// Instantiate collections
	var collections = _.clone(collectionDefs);
	$$(async.forEach)(_.keys(collections), instantiateCollection);

	// Provide hook for process-end event and teardown waterline
	$$(listenForProcessEnd)();

	// Waterline is ready-- fire the callback,
	// passing back instantiated adapters and models
	$$(cb)(null, {
		adapters: adapters,
		collections: collections
	});

	// Discover and include any "missing" adapters
	// (adapters which are defined in collections but were not passed in)
	function getMissingAdapters (collectionDefs) {
		var foundAdapterDefs = {};

		_.each(collectionDefs, function (collectionDef, collectionName) {

			// Assume default adapter if adapter is not defined
			if (!collectionDef.adapter) collectionDef.adapter = waterlineConfig.collection.adapter;

			var adapterName = collectionDef.adapter;

			// If the adapter def is not a string, something is not right
			if (!_.isString(adapterName)) throw new Error("Invalid adapter name ("+adapterName+") in collection (" + collectionName +")");

			// Already found
			else if (foundAdapterDefs[adapterName]) return;

			// User adapter defined-- use that
			else if (adapterDefs[adapterName]) foundAdapterDefs[adapterName] = adapterDefs[adapterName]();

			// If this adapter is unknown, try to require it
			else foundAdapterDefs[adapterName] = requireAdapter(adapterName, collectionName);
		});

		return foundAdapterDefs;
	}

	// Try to import an unknown adapter
	function requireAdapter(adapterName, collectionName) {

		// First, try to stat the adapter module
		var modulePath = waterlineConfig.appPath+'/node_modules/'+adapterName;
		var exists = fs.existsSync(modulePath);
		log.verbose('Loading module ' + adapterName + "...");

		// If it exists, require it.
		if (exists) return require(modulePath)();
		else {
			var err = "Unknown adapter ("+adapterName+") in collection (" + collectionName +")";
			log.error(err);
			log.error("Try running: npm install "+adapterName);
			log.error("To save the adapter as a permanent dependency, run: npm install "+adapterName+" --save");
			process.exit(1);
		}

	}

	// Adapter definition must be called as a function
	// Defining adapters as functions allows them maximum flexibility (vs. simple objects)
	function instantiateAdapter (adapterName, cb) {
		new Adapter(adapters[adapterName], function (err, adapter) {
			adapters[adapterName] = adapter;
			return cb(err, adapter);
		});
	}

	// Instantiate a new collection
	function instantiateCollection (collectionName, cb) {
		var collectionDef = collections[collectionName];

		// Get instantiated adapter for this collection
		var adapter = adapters[collectionDef.adapter];

		// Instantiate new collection using definition and adapter
		new Collection(collectionDef, adapter, function(err, collection) {
			collections[collectionName] = collection;
			return cb(err);
		});
	}

	// Assign listener for process-end
	// (this logic lives here to avoid assigning multiple events in each adapter and collection)
	function listenForProcessEnd(xcb) {
		var exiting = false;
		process.on('SIGINT', serverOnHalt);
		process.on('SIGTERM', serverOnHalt);
		// If SIGINT or SIGTERM wasn't run, this is a dirty exit--
		process.on('exit', function process_exit() {
			if (!exiting) log.warn("Server stopped itself.");
		});
		function serverOnHalt (){
			exiting = true;
			teardown(function () {
				process.exit();
			});
		}
		xcb();
	}

	// Tear down all open waterline adapters (and therefore, collections)
	function teardown (cb) {
		cb = cb || function(){};

		// Only tear down once
		if (tornDown) return cb("Waterline is already torn down!  I can't do it again!");
		tornDown = true;

		// Fire each adapter's teardown event
		async.forEach(_.values(adapters),function (adapter,cb) {
			adapter.teardown(cb);
		},cb);
	}

	// Expose public teardown() method
	module.exports.teardown = teardown;

};<|endoftext|>


<commit_before>import React, {Component} from 'react';

import DropdownSelect from 'src/DropdownSelect';
import Widget from 'src/Widget';
import WidgetHeader from 'src/WidgetHeader';
import Theme from 'src/Theme';

import PropTypeDescTable from '../PropTypeDescTable';
import DropdownSelectDoc from 'assets/propTypes/DropdownSelect.json';

import 'sass/containers/app/modules/fields/DropdownSelectExamples.scss';

export default class DropdownSelectExamples extends Component {

    constructor(props) {

        super(props);

        this.data = ['test0', 'test1', {
            text: 'test2',
            value: 2,
            desc: 'Here is test2.',
            onTouchTap() {
                console.log('test2 selected!');
            }
        }, 'test3', 'test4', 'test5', {
            text: 'test6',
            value: 6,
            desc: 'Here is test6.',
            onTouchTap() {
                console.log('test6 selected!');
            }
        }, 'test7', 'test8', 'test9'];

        this.onChange = this::this.onChange;

    }

    onChange(value) {
        console.log(value);
    }

    render() {

        return (

Dropdown Select

                    Dropdown Select use to store operating elements. Click on the contact
                    and a drop-down menu will appear. You can select from the list and execute the appropriate command.

Examples

                <Widget>

                    <WidgetHeader className="example-header" title="Basic"/>

Dropdown Select simple example.

                                    <DropdownSelect data={this.data}
                                                    onChange={this.onChange}/>

                </Widget>

                <Widget>

                    <WidgetHeader className="example-header" title="Self defined theme"/>

Set the autoClose property to false,the select list will not close when
                                    choose one item.

                                    <DropdownSelect theme={Theme.PRIMARY}
                                                    data={this.data}
                                                    onChange={this.onChange}/>

                </Widget>

                <Widget>

                    <WidgetHeader className="example-header" title="With Filter"/>

Set the filter property to true,the DropdownSelect will have user
                                        search input.

                                    <DropdownSelect data={this.data}
                                                    useFilter={true}
                                                    useSelectAll={true}
                                                    selectMode={DropdownSelect.SelectMode.MULTI_SELECT}
                                                    autoClose={false}
                                                    onChange={this.onChange}/>

                </Widget>

                <Widget>

                    <WidgetHeader className="example-header" title="With multi"/>

                                        Set the mod property to
                                        DropdownSelect.SelectMode.MULTI_SELECT,
                                        the DropdownSelect can be Multiselect.

                                    <DropdownSelect data={this.data}
                                                    autoClose={false}
                                                    selectMode={DropdownSelect.SelectMode.MULTI_SELECT}
                                                    onChange={this.onChange}/>

                </Widget>

Properties

                <PropTypeDescTable data={DropdownSelectDoc}/>

        );
    }
};<commit_msg>update DropdownSelect examples<commit_after>import React, {Component} from 'react';

import DropdownSelect from 'src/DropdownSelect';
import Widget from 'src/Widget';
import WidgetHeader from 'src/WidgetHeader';
import Theme from 'src/Theme';

import PropTypeDescTable from '../PropTypeDescTable';
import DropdownSelectDoc from 'assets/propTypes/DropdownSelect.json';

import 'sass/containers/app/modules/fields/DropdownSelectExamples.scss';

export default class DropdownSelectExamples extends Component {

    constructor(props) {

        super(props);

        this.data = ['test0', 'test1', {
            text: 'test2',
            value: 2,
            desc: 'Here is test2.',
            onTouchTap() {
                console.log('test2 selected!');
            }
        }, 'test3', 'test4', 'test5', {
            text: 'test6',
            value: 6,
            desc: 'Here is test6.',
            onTouchTap() {
                console.log('test6 selected!');
            }
        }, 'test7', 'test8', 'test9'];

        this.onChange = this::this.onChange;

    }

    onChange(value) {
        console.log(value);
    }

    render() {

        return (

Dropdown Select

                    Dropdown Select use to store operating elements. Click on the contact
                    and a drop-down menu will appear. You can select from the list and execute the appropriate command.

Examples

                <Widget>

                    <WidgetHeader className="example-header" title="Basic"/>

Dropdown Select simple example.

                                    <DropdownSelect data={this.data}
                                                    renderer={data => {
                                                        return data && typeof data === 'object' ?
                                                            `${data.text} (${data.value})`
                                                            :
                                                            `${data} (${data})`;
                                                    }}
                                                    onChange={this.onChange}/>

                </Widget>

                <Widget>

                    <WidgetHeader className="example-header" title="Self defined theme"/>

Set the autoClose property to false,the select list will not close when
                                    choose one item.

                                    <DropdownSelect theme={Theme.PRIMARY}
                                                    data={this.data}
                                                    onChange={this.onChange}/>

                </Widget>

                <Widget>

                    <WidgetHeader className="example-header" title="With Filter"/>

Set the filter property to true,the DropdownSelect will have user
                                        search input.

                                    <DropdownSelect data={this.data}
                                                    useFilter={true}
                                                    useSelectAll={true}
                                                    selectMode={DropdownSelect.SelectMode.MULTI_SELECT}
                                                    autoClose={false}
                                                    onChange={this.onChange}/>

                </Widget>

                <Widget>

                    <WidgetHeader className="example-header" title="With multi"/>

                                        Set the mod property to
                                        DropdownSelect.SelectMode.MULTI_SELECT,
                                        the DropdownSelect can be Multiselect.

                                    <DropdownSelect data={this.data}
                                                    autoClose={false}
                                                    selectMode={DropdownSelect.SelectMode.MULTI_SELECT}
                                                    onChange={this.onChange}/>

                </Widget>

Properties

                <PropTypeDescTable data={DropdownSelectDoc}/>

        );
    }
};<|endoftext|>


<commit_before>import React from 'react';

import app from '../Firebase';
import { Button, Modal, Form }  from 'react-bootstrap';

import { COLLECTION_TRIPS } from '../../constants/database.js';
import { formatTripData } from '../Utils/filter-input.js';

const db = app.firestore();

/**
 * Returns a Form.Control element with input type 'text' and other fields
 * specified by the function parameters.
 *
 * @param {string} placeholder Text placehold in the form input
 * @param {React.RefObject} ref Ref attached to the value inputted in the form.
 * @return {JSX.Element} The Form.Control element.
 */
function createTextFormControl(placeholder, ref) {
  return (
    <Form.Control
      type="text"
      placeholder={placeholder}
      ref={ref}
    />
  );
}

/**
 * Returns a Form.Control element with input type 'date' and other fields
 * specified by the function parameters.
 *
 * @param {React.RefObject} refArr The list of refs attached to the emails
 *     inputted in the form.
 * @return {JSX.Element} The Form.Control element.
 */
function createDateFormControl(defaultValue, ref) {
  return (
    <Form.Control
      type="date"
      ref={ref}
      defaultValue={defaultValue}
    />
  );
}

/**
 * Returns a Form.Control element with input type 'email' and other fields
 * specified by the function parameters.
 *
 * TODO(Issue #67): Email verification before submitting the form.
 *
 * @param {string} placeholder Text placehold in the form input
 * @param {React.RefObject} refArr The list of refs attached to the emails
 *     inputted in the form.
 * @return {JSX.Element} The Form.Control element.
 */
function createMultiFormControl(placeholder, refArr) {
  return (
    <>
      {refArr.map((ref, idx) => {
        return (
          <Form.Control
            type="email"
            placeholder={placeholder}
            ref={ref}
            key={idx}
          />
        );
      })}
    </>
  );
}

/**
 * Returns a Form.Group element with components specified by the input args.
 *
 * @param {string} controlId Prop that accessibly wires the nested label and
 *                           input prop.
 * @param {string} formLabel Label/title for the form input.
 * @param {string} inputType Input type of the form.
 * @param {string} placeholder Text placeholder in the form input.
 * @param {React.RefObject} ref Ref attached to the values inputted in the form.
 * @param {string} subFormText Subtext instructions under a form input.
 * @return {JSX.Element} The Form.Group element.
 */
function createFormGroup(controlId, formLabel, inputType, placeholder, ref) {
  let formControl;
  switch(inputType) {
    case 'text':
      formControl = createTextFormControl(placeholder, ref);
      break;
    case 'date':
      formControl = createDateFormControl(placeholder, ref);
      break;
    case 'emails':
      formControl = createMultiFormControl(placeholder, ref);
      break;
    default:
      console.error('There should be no other input type')
  }

  return (
    <Form.Group controlId={controlId}>
      <Form.Label>{formLabel}</Form.Label>
        {formControl}
    </Form.Group>
  )
}

/**
 * Component corresponding to the save trips modal.
 *
 * This component "acts" as a parent of the (non-existant) AddTripModal and
 * EditTripModal components. The only differences in the implementation between
 * the two fake components are dervied from the props `tripid` and
 * `placeholderObj` (see below).
 *
 * @param {Object} props These are the props for this component:
 * - show: Boolean that determines if the save trips modal should be displayed.
 * - handleClose: Handler that closes the save trips modal upon calling.
 * - refreshTripsContainer: Function that handles refreshing the TripsContainer
 *        component upon trip saving (Remove when fix Issue #62).
 * - tripId: For adding a new trip, this will be null. For editting an existing
 *        trip, this will the document id associated with the trip.
 * - placeholderObj: Object containing the placeholder/default values for the
 *        form input text boxes.
 * - key: Special React attribute that ensures a new AddTripModal instance is
 *        created whenever this key is updated
 *
 * @extends React.Component
 */
class SaveTripModal extends React.Component {
  /** @inheritdoc */
  constructor(props) {
    super(props);

    // Create Refs to reference form input elements
    this.nameRef = React.createRef();
    this.descriptionRef = React.createRef();
    this.destinationRef = React.createRef();
    this.startDateRef = React.createRef();
    this.endDateRef = React.createRef();

    this.isAddTripForm = this.props.tripId === null;

    // Create the number of collaborator input box refs as number of
    // collaborators specified in the placeholderObj
    const collaboratorsRefArr = [];
    for (let i = 0; i < this.props.placeholderObj.collaborators.length; i++) {
      collaboratorsRefArr.push(React.createRef())
    }
    this.state = { collaboratorsRefArr: collaboratorsRefArr }
  }

  /** Adds a new Ref element to the state variable `collaboratorsRefArr`. */
  addCollaboratorRef = () => {
    this.setState({ collaboratorsRefArr:
                      this.state.collaboratorsRefArr.concat([React.createRef()])
                  });
  }

  /**
   * Creates a new Trip document in firestore with data in `tripData`.
   *
   * @param {Object} tripData Data the new trip document will contain.
   */
  addNewTrip(tripData) {
    db.collection(COLLECTION_TRIPS)
        .add(tripData)
        .then(docRef => {
          console.log("Document written with ID: ", docRef.id);
        })
        .catch(error => {
          console.error("Error adding document: ", error);
        });
  }

  /**
   * Updates an existing Trip document in firestore with data in `tripData`.
   *
   * @param {string} tripId The document ID of the trip that is updated.
   * @param {Object} tripData Data the new trip document will contain.
   */
  updateExistingTrip(tripId, tripData) {
    db.collection(COLLECTION_TRIPS)
        .doc(tripId)
        .set(tripData)
        .then(() => {
          console.log("Document written with ID: ", tripId);
        })
        .catch(error => {
          console.error("Error adding document: ", error);
        });
  }

  /**
   * Formats/cleans the form data and saves the Trip document in firestore.
   */
  saveTrip() {
    const tripData = formatTripData(
        {
          name: this.nameRef.current.value,
          description: this.descriptionRef.current.value,
          destination: this.destinationRef.current.value,
          startDate: this.startDateRef.current.value,
          endDate: this.endDateRef.current.value,
          collaboratorEmails:
              this.state.collaboratorsRefArr.map(ref => ref.current.value),
        }
    );

    if (this.isAddTripForm) {
      this.addNewTrip(tripData);
    } else {
      this.updateExistingTrip(this.props.tripId, tripData);
    }

  }

  /**
   * Handles submission of the form which includes:
   *  - Creation of the trip.
   *  - Refreshing the trips container.
   *  - Closing the modal.
   */
  handleSubmitForm = () => {
    this.saveTrip();
    this.props.refreshTripsContainer();
    this.props.handleClose();
  }

  /** Gets the Modal title based the type of modal (edit or add trip). */
  getModalTitle = () => {
    if (this.isAddTripForm) {
      return 'Add New Trip';
    }
    return 'Edit Trip';
  }

  /** @inheritdoc */
  render() {
    return (
      <Modal show={this.props.show} onHide={this.props.handleClose}>
        <Modal.Header closeButton>
          <Modal.Title>{this.getModalTitle()}</Modal.Title>
        </Modal.Header>

        <Form>
          <Modal.Body>
            {createFormGroup('tripNameGroup', 'Trip Name', 'text',
                    this.props.placeholderObj.name, this.nameRef)}
            {createFormGroup('tripDescGroup', 'Trip Description', 'text',
                    this.props.placeholderObj.description, this.descriptionRef)}
            {createFormGroup('tripDestGroup', 'Trip Destination', 'text',
                    this.props.placeholderObj.destination, this.destinationRef)}
            {createFormGroup('tripStartDateGroup', 'Start Date', 'date',
                    this.props.placeholderObj.startDate, this.startDateRef)}
            {createFormGroup('tripEndDateGroup', 'End Date', 'date',
                    this.props.placeholderObj.endDate, this.endDateRef)}
            {createFormGroup('tripCollabsGroup', 'Trip Collaborators', 'emails',
                    this.props.placeholderObj.collaborators,
                    this.state.collaboratorsRefArr)}
            <Button onClick={this.addCollaboratorRef}>
              Add Another Collaborator
            </Button>
          </Modal.Body>

          <Modal.Footer>
            <Button variant='secondary' onClick={this.props.handleClose}>
              Cancel
            </Button>
            <Button variant='primary' onClick={this.handleSubmitForm}>
              Save Trip
            </Button>
          </Modal.Footer>
        </Form>
      </Modal>
    );
  }
};

export default SaveTripModal;
<commit_msg>Fix spelling typo.<commit_after>import React from 'react';

import app from '../Firebase';
import { Button, Modal, Form }  from 'react-bootstrap';

import { COLLECTION_TRIPS } from '../../constants/database.js';
import { formatTripData } from '../Utils/filter-input.js';

const db = app.firestore();

/**
 * Returns a Form.Control element with input type 'text' and other fields
 * specified by the function parameters.
 *
 * @param {string} placeholder Text placehold in the form input
 * @param {React.RefObject} ref Ref attached to the value inputted in the form.
 * @return {JSX.Element} The Form.Control element.
 */
function createTextFormControl(placeholder, ref) {
  return (
    <Form.Control
      type="text"
      placeholder={placeholder}
      ref={ref}
    />
  );
}

/**
 * Returns a Form.Control element with input type 'date' and other fields
 * specified by the function parameters.
 *
 * @param {React.RefObject} refArr The list of refs attached to the emails
 *     inputted in the form.
 * @return {JSX.Element} The Form.Control element.
 */
function createDateFormControl(defaultValue, ref) {
  return (
    <Form.Control
      type="date"
      ref={ref}
      defaultValue={defaultValue}
    />
  );
}

/**
 * Returns a Form.Control element with input type 'email' and other fields
 * specified by the function parameters.
 *
 * TODO(Issue #67): Email verification before submitting the form.
 *
 * @param {string} placeholder Text placehold in the form input
 * @param {React.RefObject} refArr The list of refs attached to the emails
 *     inputted in the form.
 * @return {JSX.Element} The Form.Control element.
 */
function createMultiFormControl(placeholder, refArr) {
  return (
    <>
      {refArr.map((ref, idx) => {
        return (
          <Form.Control
            type="email"
            placeholder={placeholder}
            ref={ref}
            key={idx}
          />
        );
      })}
    </>
  );
}

/**
 * Returns a Form.Group element with components specified by the input args.
 *
 * @param {string} controlId Prop that accessibly wires the nested label and
 *                           input prop.
 * @param {string} formLabel Label/title for the form input.
 * @param {string} inputType Input type of the form.
 * @param {string} placeholder Text placeholder in the form input.
 * @param {React.RefObject} ref Ref attached to the values inputted in the form.
 * @param {string} subFormText Subtext instructions under a form input.
 * @return {JSX.Element} The Form.Group element.
 */
function createFormGroup(controlId, formLabel, inputType, placeholder, ref) {
  let formControl;
  switch(inputType) {
    case 'text':
      formControl = createTextFormControl(placeholder, ref);
      break;
    case 'date':
      formControl = createDateFormControl(placeholder, ref);
      break;
    case 'emails':
      formControl = createMultiFormControl(placeholder, ref);
      break;
    default:
      console.error('There should be no other input type')
  }

  return (
    <Form.Group controlId={controlId}>
      <Form.Label>{formLabel}</Form.Label>
        {formControl}
    </Form.Group>
  )
}

/**
 * Component corresponding to the save trips modal.
 *
 * This component "acts" as a parent of the (non-existent) AddTripModal and
 * EditTripModal components. The only differences in the implementation between
 * the two fake components are dervied from the props `tripid` and
 * `placeholderObj` (see below).
 *
 * @param {Object} props These are the props for this component:
 * - show: Boolean that determines if the save trips modal should be displayed.
 * - handleClose: Handler that closes the save trips modal upon calling.
 * - refreshTripsContainer: Function that handles refreshing the TripsContainer
 *        component upon trip saving (Remove when fix Issue #62).
 * - tripId: For adding a new trip, this will be null. For editting an existing
 *        trip, this will the document id associated with the trip.
 * - placeholderObj: Object containing the placeholder/default values for the
 *        form input text boxes.
 * - key: Special React attribute that ensures a new AddTripModal instance is
 *        created whenever this key is updated
 *
 * @extends React.Component
 */
class SaveTripModal extends React.Component {
  /** @inheritdoc */
  constructor(props) {
    super(props);

    // Create Refs to reference form input elements
    this.nameRef = React.createRef();
    this.descriptionRef = React.createRef();
    this.destinationRef = React.createRef();
    this.startDateRef = React.createRef();
    this.endDateRef = React.createRef();

    this.isAddTripForm = this.props.tripId === null;

    // Create the number of collaborator input box refs as number of
    // collaborators specified in the placeholderObj
    const collaboratorsRefArr = [];
    for (let i = 0; i < this.props.placeholderObj.collaborators.length; i++) {
      collaboratorsRefArr.push(React.createRef())
    }
    this.state = { collaboratorsRefArr: collaboratorsRefArr }
  }

  /** Adds a new Ref element to the state variable `collaboratorsRefArr`. */
  addCollaboratorRef = () => {
    this.setState({ collaboratorsRefArr:
                      this.state.collaboratorsRefArr.concat([React.createRef()])
                  });
  }

  /**
   * Creates a new Trip document in firestore with data in `tripData`.
   *
   * @param {Object} tripData Data the new trip document will contain.
   */
  addNewTrip(tripData) {
    db.collection(COLLECTION_TRIPS)
        .add(tripData)
        .then(docRef => {
          console.log("Document written with ID: ", docRef.id);
        })
        .catch(error => {
          console.error("Error adding document: ", error);
        });
  }

  /**
   * Updates an existing Trip document in firestore with data in `tripData`.
   *
   * @param {string} tripId The document ID of the trip that is updated.
   * @param {Object} tripData Data the new trip document will contain.
   */
  updateExistingTrip(tripId, tripData) {
    db.collection(COLLECTION_TRIPS)
        .doc(tripId)
        .set(tripData)
        .then(() => {
          console.log("Document written with ID: ", tripId);
        })
        .catch(error => {
          console.error("Error adding document: ", error);
        });
  }

  /**
   * Formats/cleans the form data and saves the Trip document in firestore.
   */
  saveTrip() {
    const tripData = formatTripData(
        {
          name: this.nameRef.current.value,
          description: this.descriptionRef.current.value,
          destination: this.destinationRef.current.value,
          startDate: this.startDateRef.current.value,
          endDate: this.endDateRef.current.value,
          collaboratorEmails:
              this.state.collaboratorsRefArr.map(ref => ref.current.value),
        }
    );

    if (this.isAddTripForm) {
      this.addNewTrip(tripData);
    } else {
      this.updateExistingTrip(this.props.tripId, tripData);
    }

  }

  /**
   * Handles submission of the form which includes:
   *  - Creation of the trip.
   *  - Refreshing the trips container.
   *  - Closing the modal.
   */
  handleSubmitForm = () => {
    this.saveTrip();
    this.props.refreshTripsContainer();
    this.props.handleClose();
  }

  /** Gets the Modal title based the type of modal (edit or add trip). */
  getModalTitle = () => {
    if (this.isAddTripForm) {
      return 'Add New Trip';
    }
    return 'Edit Trip';
  }

  /** @inheritdoc */
  render() {
    return (
      <Modal show={this.props.show} onHide={this.props.handleClose}>
        <Modal.Header closeButton>
          <Modal.Title>{this.getModalTitle()}</Modal.Title>
        </Modal.Header>

        <Form>
          <Modal.Body>
            {createFormGroup('tripNameGroup', 'Trip Name', 'text',
                    this.props.placeholderObj.name, this.nameRef)}
            {createFormGroup('tripDescGroup', 'Trip Description', 'text',
                    this.props.placeholderObj.description, this.descriptionRef)}
            {createFormGroup('tripDestGroup', 'Trip Destination', 'text',
                    this.props.placeholderObj.destination, this.destinationRef)}
            {createFormGroup('tripStartDateGroup', 'Start Date', 'date',
                    this.props.placeholderObj.startDate, this.startDateRef)}
            {createFormGroup('tripEndDateGroup', 'End Date', 'date',
                    this.props.placeholderObj.endDate, this.endDateRef)}
            {createFormGroup('tripCollabsGroup', 'Trip Collaborators', 'emails',
                    this.props.placeholderObj.collaborators,
                    this.state.collaboratorsRefArr)}
            <Button onClick={this.addCollaboratorRef}>
              Add Another Collaborator
            </Button>
          </Modal.Body>

          <Modal.Footer>
            <Button variant='secondary' onClick={this.props.handleClose}>
              Cancel
            </Button>
            <Button variant='primary' onClick={this.handleSubmitForm}>
              Save Trip
            </Button>
          </Modal.Footer>
        </Form>
      </Modal>
    );
  }
};

export default SaveTripModal;
<|endoftext|>