<commit_before>const Plugin = require('../Plugin')
const Client = require('./Client')
const StatusSocket = require('./Socket')

/**
 * Upload files to Transloadit using Tus.
 */
module.exports = class Transloadit extends Plugin {
  constructor (core, opts) {
    super(core, opts)
    this.type = 'uploader'
    this.id = 'Transloadit'
    this.title = 'Transloadit'

    const defaultLocale = {
      strings: {
        creatingAssembly: 'Preparing upload...',
        creatingAssemblyFailed: 'Transloadit: Could not create assembly',
        encoding: 'Encoding...'
      }
    }

    const defaultOptions = {
      waitForEncoding: false,
      waitForMetadata: false,
      signature: null,
      params: null,
      fields: {},
      getAssemblyOptions (file, options) {
        return {
          params: options.params,
          signature: options.signature,
          fields: options.fields
        }
      },
      locale: defaultLocale
    }

    this.opts = Object.assign({}, defaultOptions, opts)

    this.locale = Object.assign({}, defaultLocale, this.opts.locale)
    this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings)

    this.prepareUpload = this.prepareUpload.bind(this)
    this.afterUpload = this.afterUpload.bind(this)

    if (this.opts.params) {
      this.validateParams(this.opts.params)
    }

    this.client = new Client()
    this.sockets = {}
  }

  validateParams (params) {
    if (!params) {
      throw new Error('Transloadit: The `params` option is required.')
    }

    if (typeof params === 'string') {
      try {
        params = JSON.parse(params)
      } catch (err) {
        // Tell the user that this is not an Uppy bug!
        err.message = 'Transloadit: The `params` option is a malformed JSON string: ' +
          err.message
        throw err
      }
    }

    if (!params.auth || !params.auth.key) {
      throw new Error('Transloadit: The `params.auth.key` option is required. ' +
        'You can find your Transloadit API key at https://transloadit.com/accounts/credentials.')
    }
  }

  getAssemblyOptions (fileIDs) {
    const options = this.opts
    return Promise.all(
      fileIDs.map((fileID) => {
        const file = this.core.getFile(fileID)
        const promise = Promise.resolve(options.getAssemblyOptions(file, options))
        return promise.then((assemblyOptions) => {
          this.validateParams(assemblyOptions.params)

          return {
            fileIDs: [fileID],
            options: assemblyOptions
          }
        })
      })
    )
  }

  dedupeAssemblyOptions (list) {
    const dedupeMap = Object.create(null)
    list.forEach(({ fileIDs, options }) => {
      const id = JSON.stringify(options)
      if (dedupeMap[id]) {
        dedupeMap[id].fileIDs.push(...fileIDs)
      } else {
        dedupeMap[id] = {
          options,
          fileIDs: [...fileIDs]
        }
      }
    })

    return Object.keys(dedupeMap).map((id) => dedupeMap[id])
  }

  createAssembly (fileIDs, options) {
    this.core.log('Transloadit: create assembly')

    return this.client.createAssembly({
      params: options.params,
      fields: options.fields,
      expectedFiles: fileIDs.length,
      signature: options.signature
    }).then((assembly) => {
      this.updateState({
        assemblies: Object.assign(this.state.assemblies, {
          [assembly.assembly_id]: assembly
        })
      })

      function attachAssemblyMetadata (file, assembly) {
        // Attach meta parameters for the Tus plugin. See:
        // https://github.com/tus/tusd/wiki/Uploading-to-Transloadit-using-tus#uploading-using-tus
        // TODO Should this `meta` be moved to a `tus.meta` property instead?
        // If the MetaData plugin can add eg. resize parameters, it doesn't
        // make much sense to set those as upload-metadata for tus.
        const meta = Object.assign({}, file.meta, {
          assembly_url: assembly.assembly_url,
          filename: file.name,
          fieldname: 'file'
        })
        // Add assembly-specific Tus endpoint.
        const tus = Object.assign({}, file.tus, {
          endpoint: assembly.tus_url
        })
        const transloadit = {
          assembly: assembly.assembly_id
        }
        return Object.assign(
          {},
          file,
          { meta, tus, transloadit }
        )
      }

      const files = Object.assign({}, this.core.state.files)
      fileIDs.forEach((id) => {
        files[id] = attachAssemblyMetadata(files[id], assembly)
      })

      this.core.setState({ files })

      this.core.emit('transloadit:assembly', assembly, fileIDs)

      return this.connectSocket(assembly)
    }).then(() => {
      this.core.log('Transloadit: Created assembly')
    }).catch((err) => {
      this.core.emit('informer', this.opts.locale.strings.creatingAssemblyFailed, 'error', 0)

      // Reject the promise.
      throw err
    })
  }

  shouldWait () {
    return this.opts.waitForEncoding || this.opts.waitForMetadata
  }

  findFile (uploadedFile) {
    const files = this.core.state.files
    for (const id in files) {
      if (!files.hasOwnProperty(id)) {
        continue
      }
      if (files[id].uploadURL === uploadedFile.tus_upload_url) {
        return files[id]
      }
    }
  }

  onFileUploadComplete (assemblyId, uploadedFile) {
    const file = this.findFile(uploadedFile)
    this.updateState({
      files: Object.assign({}, this.state.files, {
        [uploadedFile.id]: {
          id: file.id,
          uploadedFile
        }
      })
    })
    this.core.bus.emit('transloadit:upload', uploadedFile, this.getAssembly(assemblyId))
  }

  onResult (assemblyId, stepName, result) {
    const file = this.state.files[result.original_id]
    // The `file` may not exist if an import robot was used instead of a file upload.
    result.localId = file ? file.id : null

    this.updateState({
      results: this.state.results.concat(result)
    })
    this.core.bus.emit('transloadit:result', stepName, result, this.getAssembly(assemblyId))
  }

  onAssemblyFinished (url) {
    this.client.getAssemblyStatus(url).then((assembly) => {
      this.updateState({
        assemblies: Object.assign({}, this.state.assemblies, {
          [assembly.assembly_id]: assembly
        })
      })
      this.core.emit('transloadit:complete', assembly)
    })
  }

  connectSocket (assembly) {
    const socket = new StatusSocket(
      assembly.websocket_url,
      assembly
    )
    this.sockets[assembly.assembly_id] = socket

    socket.on('upload', this.onFileUploadComplete.bind(this, assembly.assembly_id))
    socket.on('error', (error) => {
      this.core.emit('transloadit:assembly-error', assembly, error)
    })

    if (this.opts.waitForEncoding) {
      socket.on('result', this.onResult.bind(this, assembly.assembly_id))
    }

    if (this.opts.waitForEncoding) {
      socket.on('finished', () => {
        this.onAssemblyFinished(assembly.assembly_ssl_url)
      })
    } else if (this.opts.waitForMetadata) {
      socket.on('metadata', () => {
        this.onAssemblyFinished(assembly.assembly_ssl_url)
        this.core.emit('transloadit:complete', assembly)
      })
    }

    return new Promise((resolve, reject) => {
      socket.on('connect', resolve)
      socket.on('error', reject)
    }).then(() => {
      this.core.log('Transloadit: Socket is ready')
    })
  }

  prepareUpload (fileIDs) {
    fileIDs.forEach((fileID) => {
      this.core.emit('core:preprocess-progress', fileID, {
        mode: 'indeterminate',
        message: this.opts.locale.strings.creatingAssembly
      })
    })

    const createAssembly = ({ fileIDs, options }) => {
      return this.createAssembly(fileIDs, options).then(() => {
        fileIDs.forEach((fileID) => {
          this.core.emit('core:preprocess-complete', fileID)
        })
      })
    }

    return this.getAssemblyOptions(fileIDs)
      .then((allOptions) => this.dedupeAssemblyOptions(allOptions))
      .then((assemblies) => Promise.all(
        assemblies.map(createAssembly)
      ))
  }

  afterUpload (fileIDs) {
    // A file ID that is part of this assembly...
    const fileID = fileIDs[0]

    // If we don't have to wait for encoding metadata or results, we can close
    // the socket immediately and finish the upload.
    if (!this.shouldWait()) {
      const file = this.core.getFile(fileID)
      const socket = this.socket[file.assembly]
      socket.close()
      return
    }

    return new Promise((resolve, reject) => {
      fileIDs.forEach((fileID) => {
        this.core.emit('core:postprocess-progress', fileID, {
          mode: 'indeterminate',
          message: this.opts.locale.strings.encoding
        })
      })

      const onAssemblyFinished = (assembly) => {
        const file = this.core.getFile(fileID)
        // An assembly for a different upload just finished. We can ignore it.
        if (assembly.assembly_id !== file.transloadit.assembly) {
          return
        }
        // Remove this handler once we find the assembly we needed.
        this.core.emitter.off('transloadit:complete', onAssemblyFinished)

        // TODO set the `file.uploadURL` to a result?
        // We will probably need an option here so the plugin user can tell us
        // which result to pick…?

        fileIDs.forEach((fileID) => {
          this.core.emit('core:postprocess-complete', fileID)
        })

        resolve()
      }

      const onAssemblyError = (assembly, error) => {
        const file = this.core.getFile(fileID)
        // An assembly for a different upload just errored. We can ignore it.
        if (assembly.assembly_id !== file.transloadit.assembly) {
          return
        }
        // Remove this handler once we find the assembly we needed.
        this.core.emitter.off('transloadit:assembly-error', onAssemblyError)

        // Clear postprocessing state for all our files.
        fileIDs.forEach((fileID) => {
          this.core.emit('core:postprocess-complete', fileID)
        })

        // Reject the `afterUpload()` promise.
        reject(error)
      }

      this.core.on('transloadit:complete', onAssemblyFinished)
      this.core.on('transloadit:assembly-error', onAssemblyError)
    })
  }

  install () {
    this.core.addPreProcessor(this.prepareUpload)
    this.core.addPostProcessor(this.afterUpload)

    this.updateState({
      assemblies: {},
      files: {},
      results: []
    })
  }

  uninstall () {
    this.core.removePreProcessor(this.prepareUpload)
    this.core.removePostProcessor(this.afterUpload)
  }

  getAssembly (id) {
    return this.state.assemblies[id]
  }

  get state () {
    return this.core.state.transloadit || {}
  }

  updateState (newState) {
    const transloadit = Object.assign({}, this.state, newState)

    this.core.setState({ transloadit })
  }
}
<commit_msg>transloadit: Rename `:assembly` event to `:assembly-created`<commit_after>const Plugin = require('../Plugin')
const Client = require('./Client')
const StatusSocket = require('./Socket')

/**
 * Upload files to Transloadit using Tus.
 */
module.exports = class Transloadit extends Plugin {
  constructor (core, opts) {
    super(core, opts)
    this.type = 'uploader'
    this.id = 'Transloadit'
    this.title = 'Transloadit'

    const defaultLocale = {
      strings: {
        creatingAssembly: 'Preparing upload...',
        creatingAssemblyFailed: 'Transloadit: Could not create assembly',
        encoding: 'Encoding...'
      }
    }

    const defaultOptions = {
      waitForEncoding: false,
      waitForMetadata: false,
      signature: null,
      params: null,
      fields: {},
      getAssemblyOptions (file, options) {
        return {
          params: options.params,
          signature: options.signature,
          fields: options.fields
        }
      },
      locale: defaultLocale
    }

    this.opts = Object.assign({}, defaultOptions, opts)

    this.locale = Object.assign({}, defaultLocale, this.opts.locale)
    this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings)

    this.prepareUpload = this.prepareUpload.bind(this)
    this.afterUpload = this.afterUpload.bind(this)

    if (this.opts.params) {
      this.validateParams(this.opts.params)
    }

    this.client = new Client()
    this.sockets = {}
  }

  validateParams (params) {
    if (!params) {
      throw new Error('Transloadit: The `params` option is required.')
    }

    if (typeof params === 'string') {
      try {
        params = JSON.parse(params)
      } catch (err) {
        // Tell the user that this is not an Uppy bug!
        err.message = 'Transloadit: The `params` option is a malformed JSON string: ' +
          err.message
        throw err
      }
    }

    if (!params.auth || !params.auth.key) {
      throw new Error('Transloadit: The `params.auth.key` option is required. ' +
        'You can find your Transloadit API key at https://transloadit.com/accounts/credentials.')
    }
  }

  getAssemblyOptions (fileIDs) {
    const options = this.opts
    return Promise.all(
      fileIDs.map((fileID) => {
        const file = this.core.getFile(fileID)
        const promise = Promise.resolve(options.getAssemblyOptions(file, options))
        return promise.then((assemblyOptions) => {
          this.validateParams(assemblyOptions.params)

          return {
            fileIDs: [fileID],
            options: assemblyOptions
          }
        })
      })
    )
  }

  dedupeAssemblyOptions (list) {
    const dedupeMap = Object.create(null)
    list.forEach(({ fileIDs, options }) => {
      const id = JSON.stringify(options)
      if (dedupeMap[id]) {
        dedupeMap[id].fileIDs.push(...fileIDs)
      } else {
        dedupeMap[id] = {
          options,
          fileIDs: [...fileIDs]
        }
      }
    })

    return Object.keys(dedupeMap).map((id) => dedupeMap[id])
  }

  createAssembly (fileIDs, options) {
    this.core.log('Transloadit: create assembly')

    return this.client.createAssembly({
      params: options.params,
      fields: options.fields,
      expectedFiles: fileIDs.length,
      signature: options.signature
    }).then((assembly) => {
      this.updateState({
        assemblies: Object.assign(this.state.assemblies, {
          [assembly.assembly_id]: assembly
        })
      })

      function attachAssemblyMetadata (file, assembly) {
        // Attach meta parameters for the Tus plugin. See:
        // https://github.com/tus/tusd/wiki/Uploading-to-Transloadit-using-tus#uploading-using-tus
        // TODO Should this `meta` be moved to a `tus.meta` property instead?
        // If the MetaData plugin can add eg. resize parameters, it doesn't
        // make much sense to set those as upload-metadata for tus.
        const meta = Object.assign({}, file.meta, {
          assembly_url: assembly.assembly_url,
          filename: file.name,
          fieldname: 'file'
        })
        // Add assembly-specific Tus endpoint.
        const tus = Object.assign({}, file.tus, {
          endpoint: assembly.tus_url
        })
        const transloadit = {
          assembly: assembly.assembly_id
        }
        return Object.assign(
          {},
          file,
          { meta, tus, transloadit }
        )
      }

      const files = Object.assign({}, this.core.state.files)
      fileIDs.forEach((id) => {
        files[id] = attachAssemblyMetadata(files[id], assembly)
      })

      this.core.setState({ files })

      this.core.emit('transloadit:assembly-created', assembly, fileIDs)

      return this.connectSocket(assembly)
    }).then(() => {
      this.core.log('Transloadit: Created assembly')
    }).catch((err) => {
      this.core.emit('informer', this.opts.locale.strings.creatingAssemblyFailed, 'error', 0)

      // Reject the promise.
      throw err
    })
  }

  shouldWait () {
    return this.opts.waitForEncoding || this.opts.waitForMetadata
  }

  findFile (uploadedFile) {
    const files = this.core.state.files
    for (const id in files) {
      if (!files.hasOwnProperty(id)) {
        continue
      }
      if (files[id].uploadURL === uploadedFile.tus_upload_url) {
        return files[id]
      }
    }
  }

  onFileUploadComplete (assemblyId, uploadedFile) {
    const file = this.findFile(uploadedFile)
    this.updateState({
      files: Object.assign({}, this.state.files, {
        [uploadedFile.id]: {
          id: file.id,
          uploadedFile
        }
      })
    })
    this.core.bus.emit('transloadit:upload', uploadedFile, this.getAssembly(assemblyId))
  }

  onResult (assemblyId, stepName, result) {
    const file = this.state.files[result.original_id]
    // The `file` may not exist if an import robot was used instead of a file upload.
    result.localId = file ? file.id : null

    this.updateState({
      results: this.state.results.concat(result)
    })
    this.core.bus.emit('transloadit:result', stepName, result, this.getAssembly(assemblyId))
  }

  onAssemblyFinished (url) {
    this.client.getAssemblyStatus(url).then((assembly) => {
      this.updateState({
        assemblies: Object.assign({}, this.state.assemblies, {
          [assembly.assembly_id]: assembly
        })
      })
      this.core.emit('transloadit:complete', assembly)
    })
  }

  connectSocket (assembly) {
    const socket = new StatusSocket(
      assembly.websocket_url,
      assembly
    )
    this.sockets[assembly.assembly_id] = socket

    socket.on('upload', this.onFileUploadComplete.bind(this, assembly.assembly_id))
    socket.on('error', (error) => {
      this.core.emit('transloadit:assembly-error', assembly, error)
    })

    if (this.opts.waitForEncoding) {
      socket.on('result', this.onResult.bind(this, assembly.assembly_id))
    }

    if (this.opts.waitForEncoding) {
      socket.on('finished', () => {
        this.onAssemblyFinished(assembly.assembly_ssl_url)
      })
    } else if (this.opts.waitForMetadata) {
      socket.on('metadata', () => {
        this.onAssemblyFinished(assembly.assembly_ssl_url)
        this.core.emit('transloadit:complete', assembly)
      })
    }

    return new Promise((resolve, reject) => {
      socket.on('connect', resolve)
      socket.on('error', reject)
    }).then(() => {
      this.core.log('Transloadit: Socket is ready')
    })
  }

  prepareUpload (fileIDs) {
    fileIDs.forEach((fileID) => {
      this.core.emit('core:preprocess-progress', fileID, {
        mode: 'indeterminate',
        message: this.opts.locale.strings.creatingAssembly
      })
    })

    const createAssembly = ({ fileIDs, options }) => {
      return this.createAssembly(fileIDs, options).then(() => {
        fileIDs.forEach((fileID) => {
          this.core.emit('core:preprocess-complete', fileID)
        })
      })
    }

    return this.getAssemblyOptions(fileIDs)
      .then((allOptions) => this.dedupeAssemblyOptions(allOptions))
      .then((assemblies) => Promise.all(
        assemblies.map(createAssembly)
      ))
  }

  afterUpload (fileIDs) {
    // A file ID that is part of this assembly...
    const fileID = fileIDs[0]

    // If we don't have to wait for encoding metadata or results, we can close
    // the socket immediately and finish the upload.
    if (!this.shouldWait()) {
      const file = this.core.getFile(fileID)
      const socket = this.socket[file.assembly]
      socket.close()
      return
    }

    return new Promise((resolve, reject) => {
      fileIDs.forEach((fileID) => {
        this.core.emit('core:postprocess-progress', fileID, {
          mode: 'indeterminate',
          message: this.opts.locale.strings.encoding
        })
      })

      const onAssemblyFinished = (assembly) => {
        const file = this.core.getFile(fileID)
        // An assembly for a different upload just finished. We can ignore it.
        if (assembly.assembly_id !== file.transloadit.assembly) {
          return
        }
        // Remove this handler once we find the assembly we needed.
        this.core.emitter.off('transloadit:complete', onAssemblyFinished)

        // TODO set the `file.uploadURL` to a result?
        // We will probably need an option here so the plugin user can tell us
        // which result to pick…?

        fileIDs.forEach((fileID) => {
          this.core.emit('core:postprocess-complete', fileID)
        })

        resolve()
      }

      const onAssemblyError = (assembly, error) => {
        const file = this.core.getFile(fileID)
        // An assembly for a different upload just errored. We can ignore it.
        if (assembly.assembly_id !== file.transloadit.assembly) {
          return
        }
        // Remove this handler once we find the assembly we needed.
        this.core.emitter.off('transloadit:assembly-error', onAssemblyError)

        // Clear postprocessing state for all our files.
        fileIDs.forEach((fileID) => {
          this.core.emit('core:postprocess-complete', fileID)
        })

        // Reject the `afterUpload()` promise.
        reject(error)
      }

      this.core.on('transloadit:complete', onAssemblyFinished)
      this.core.on('transloadit:assembly-error', onAssemblyError)
    })
  }

  install () {
    this.core.addPreProcessor(this.prepareUpload)
    this.core.addPostProcessor(this.afterUpload)

    this.updateState({
      assemblies: {},
      files: {},
      results: []
    })
  }

  uninstall () {
    this.core.removePreProcessor(this.prepareUpload)
    this.core.removePostProcessor(this.afterUpload)
  }

  getAssembly (id) {
    return this.state.assemblies[id]
  }

  get state () {
    return this.core.state.transloadit || {}
  }

  updateState (newState) {
    const transloadit = Object.assign({}, this.state, newState)

    this.core.setState({ transloadit })
  }
}
<|endoftext|>


<commit_before>(function () {

    var View = Backbone.View.extend({
        events: {
        },

        initialize: function (options) {
            this.$el = $($('#tmpl_channel_list').html());

            this.channels = [];

            // Some of this code makes IE go completely crazy, so we have to browser sniff
            // It's horrible, I know, but otherwise a Very Bad Thing™ will happen.
            this.isIE = navigator.appName === 'Microsoft Internet Explorer';

            // Sort the table by num. users?
            this.ordered = !this.isIE;

            // Waiting to add the table back into the DOM?
            this.waiting = false;
        },

        render: function () {
            var table = $('table', this.$el),
                tbody = table.children('tbody:first').detach(),
                that = this;
            if (!this.isIE) {
                tbody.children().each(function (idx, child) {
                    var i, chan,
                        channels_length = that.channels.length;
                    chan = $(child.querySelector('.chan')).data('channel');
                    for (i = 0; i < channels_length; i++) {
                        if (that.channels[i].channel === chan) {
                            that.channels[i].dom = tbody[0].removeChild(child);
                            break;
                        }
                    }
                });
            }

            if (this.ordered) {
                this.channels.sort(function (a, b) {
                    return b.num_users - a.num_users;
                });
            }

            _.each(this.channels, function (chan) {
                tbody[0].appendChild(chan.dom);
            });
            table[0].appendChild(tbody[0]);
        }
    });

    var Applet = Backbone.Model.extend({
        initialize: function () {
            this.set('title', 'Channel List');
            this.view = new View();

            this.network = _kiwi.global.components.Network();
            this.network.on('onlist_channel', this.onListChannel, this);
            this.network.on('onlist_start', this.onListStart, this);
        },

        // New channels to add to our list
        onListChannel: function (event) {
            this.addChannel(event.chans);
        },

        // A new, fresh channel list starting
        onListStart: function (event) {
            // TODO: clear out our existing list
        },

        addChannel: function (channels) {
            var that = this;

            if (!_.isArray(channels)) {
                channels = [channels];
            }
            _.each(channels, function (chan) {
                var row;
                row = document.createElement("tr");
                row.innerHTML = '' + _.escape(chan.channel) + '	' + chan.num_users + '	' + formatIRCMsg(_.escape(chan.topic)) + '	';
                chan.dom = row;
                that.view.channels.push(chan);
            });

            if (!that.view.waiting) {
                that.view.waiting = true;
                _.defer(function () {
                    that.view.render();
                    that.view.waiting = false;
                });
            }
        },

        dispose: function () {
            this.view.channels = null;
            this.view.unbind();
            this.view.$el.html('');
            this.view.remove();
            this.view = null;

            // Remove any network event bindings
            this.network.off();
        }
    });

    _kiwi.model.Applet.register('kiwi_chanlist', Applet);
})();<commit_msg>Optimise chanlist render()<commit_after>(function () {

    var View = Backbone.View.extend({
        events: {
        },

        initialize: function (options) {
            this.$el = $($('#tmpl_channel_list').html());

            this.channels = [];

            // Some of this code makes IE go completely crazy, so we have to browser sniff
            // It's horrible, I know, but otherwise a Very Bad Thing™ will happen.
            this.isIE = navigator.appName === 'Microsoft Internet Explorer';

            // Sort the table by num. users?
            this.ordered = !this.isIE;

            // Waiting to add the table back into the DOM?
            this.waiting = false;
        },

        render: function () {
            var table = $('table', this.$el),
                tbody = table.children('tbody:first').detach(),
                that = this,
                channels_length = this.channels.length,
                i;

            tbody.children().each(function (idx, child) {
                if (that.channels[idx].channel === $(child.querySelector('.chan')).data('channel')) {
                    that.channels[idx].dom = tbody[0].removeChild(child);
                }
            });

            if (this.ordered) {
                this.channels.sort(function (a, b) {
                    return b.num_users - a.num_users;
                });
            }

            for (i = 0; i < channels_length; i++) {
                tbody[0].appendChild(this.channels[i].dom);
            }
            table[0].appendChild(tbody[0]);
        }
    });

    var Applet = Backbone.Model.extend({
        initialize: function () {
            this.set('title', 'Channel List');
            this.view = new View();

            this.network = _kiwi.global.components.Network();
            this.network.on('onlist_channel', this.onListChannel, this);
            this.network.on('onlist_start', this.onListStart, this);
        },

        // New channels to add to our list
        onListChannel: function (event) {
            this.addChannel(event.chans);
        },

        // A new, fresh channel list starting
        onListStart: function (event) {
            // TODO: clear out our existing list
        },

        addChannel: function (channels) {
            var that = this;

            if (!_.isArray(channels)) {
                channels = [channels];
            }
            _.each(channels, function (chan) {
                var row;
                row = document.createElement("tr");
                row.innerHTML = '' + _.escape(chan.channel) + '	' + chan.num_users + '	' + formatIRCMsg(_.escape(chan.topic)) + '	';
                chan.dom = row;
                that.view.channels.push(chan);
            });

            if (!that.view.waiting) {
                that.view.waiting = true;
                _.defer(function () {
                    that.view.render();
                    that.view.waiting = false;
                });
            }
        },

        dispose: function () {
            this.view.channels = null;
            this.view.unbind();
            this.view.$el.html('');
            this.view.remove();
            this.view = null;

            // Remove any network event bindings
            this.network.off();
        }
    });

    _kiwi.model.Applet.register('kiwi_chanlist', Applet);
})();<|endoftext|>


<commit_before>import Component from '../../view/components/component';
import {VIEW_CORE_NAME} from '@grid/view/definition';
import {BackdropView} from '@grid/plugin/backdrop/backdrop.view';

class Backdrop extends Component {
	constructor($element, $document) {
		super();

		this.document = $document[0];
		this.element = $element[0];
	}

	onInit() {
		const backdrop = new BackdropView(this);
		this.using(backdrop.closeEvent.on(this.onCloseCore.bind(this)));
	}

	onCloseCore(e) {
		const apply = this.$view.root.applyFactory(null, 'sync');
		apply(() => this.onClose({$event: e}));
	}
}

Backdrop.$inject = ['$element', '$document', '$scope'];

export default {
	transclude: true,
	templateUrl: 'qgrid.plugin.backdrop.tpl.html',
	controller: Backdrop,
	controllerAs: '$backdropPlugin',
	require: {
		$view: `^^${VIEW_CORE_NAME}`
	},
	bindings: {
		'onClose': '&',
		'onKeyDown': '&',
		'propagate': '@'
	}
};
<commit_msg>clean code<commit_after>import Component from '../../view/components/component';
import {VIEW_CORE_NAME} from '@grid/view/definition';
import {BackdropView} from '@grid/plugin/backdrop/backdrop.view';

class Backdrop extends Component {
	constructor($element, $document) {
		super();

		this.document = $document[0];
		this.element = $element[0];
	}

	onInit() {
		const backdrop = new BackdropView(this);
		this.using(backdrop.closeEvent.on(this.onCloseCore.bind(this)));
	}

	onCloseCore(e) {
		const apply = this.$view.root.applyFactory(null, 'sync');
		apply(() => this.onClose({$event: e}));
	}
}

Backdrop.$inject = ['$element', '$document'];

export default {
	transclude: true,
	templateUrl: 'qgrid.plugin.backdrop.tpl.html',
	controller: Backdrop,
	controllerAs: '$backdropPlugin',
	require: {
		$view: `^^${VIEW_CORE_NAME}`
	},
	bindings: {
		'onClose': '&',
		'onKeyDown': '&',
		'propagate': '@'
	}
};
<|endoftext|>


<commit_before>/**
 * Created by wujichao on 16/4/28.
 */
/*
 headerView
 section cell(可配置cell)?  先title subtitle + jumpUrl
 footerView
     */

"use strict";

import React from 'react';

import {
    Cells,
    CellsTitle,
    CellsTips,
    Cell,
    CellHeader,
    CellBody,
    CellFooter,
} from 'react-weui';

export default class ListView extends React.Component {
    constructor (props) {
        super(props);

        // this.state = { smiling: false };
        //
        // this.handleClick = () => {
        //     this.setState({smiling: !this.state.smiling});
        // };
    }

    componentWillMount () {
        // add event listeners (Flux Store, WebSocket, document, etc.)
    }

    componentDidMount () {
        // React.getDOMNode()
    }

    componentWillUnmount () {
        // remove event listeners (Flux Store, WebSocket, document, etc.)
    }

    render() {

        var section_titles = this.props.sections.map((section) => {
            return (<CellsTitle>{section.header.title}</CellsTitle>);
        });

        var section_rows = this.props.sections.map((section) => {
            var rows = [];
            for (var i = 0; i < section.rows.length; i++) {
                var item = section.rows[i];
                rows.push(<Cell>
                    <CellBody>
                        {item.title}
                    </CellBody>
                    <CellFooter>
                        {item.subTitle}
                    </CellFooter>
                </Cell>);
            }
            return (<Cells>{rows}</Cells>);
        });

        var table = [];
        for (var i = 0; i < section_titles.length; i++) {
            table.push(section_titles[i]);
            table.push(section_rows[i]);
        }

        var components = [];
        if (this.props.headerView) {
            components.push(this.props.headerView)
        }
        if (table.length > 0) {
            components.push(table);
        }
        if (this.props.footerView) {
            components.push(this.props.footerView)
        }

        if (components.length === 0) {
            return (null);
        } else {
            return (

                    {components}

            );
        }
    }
}

/*
 sections =  [
    {
        header:{title:'', ...},
        rows:[{}, {}]
    }
 ]
 */
ListView.defaultProps = {
    sections: []
};

ListView.propTypes = {
    sections: React.PropTypes.array,
    headerView: React.PropTypes.element,
    footerView: React.PropTypes.element,
};
<commit_msg>加上跳转支持<commit_after>/**
 * Created by wujichao on 16/4/28.
 */
/*
 headerView
 section cell(可配置cell)?  先title subtitle + jumpUrl
 footerView
     */

"use strict";

import React from 'react';

import {
    Cells,
    CellsTitle,
    CellsTips,
    Cell,
    CellHeader,
    CellBody,
    CellFooter,
} from 'react-weui';

export default class ListView extends React.Component {
    constructor (props) {
        super(props);

        // this.state = { smiling: false };
        //
        // this.handleClick = () => {
        //     this.setState({smiling: !this.state.smiling});
        // };
    }

    componentWillMount () {
        // add event listeners (Flux Store, WebSocket, document, etc.)
    }

    componentDidMount () {
        // React.getDOMNode()
    }

    componentWillUnmount () {
        // remove event listeners (Flux Store, WebSocket, document, etc.)
    }

    render() {

        var section_titles = this.props.sections.map((section) => {
            return (<CellsTitle>{section.header.title}</CellsTitle>);
        });

        var section_rows = this.props.sections.map((section) => {
            var rows = [];
            for (var i = 0; i < section.rows.length; i++) {
                var item = section.rows[i];
                rows.push(<Cell href={item.jumpUrl}>
                    <CellBody>
                        {item.title}
                    </CellBody>
                    <CellFooter>
                        {item.subTitle}
                    </CellFooter>
                </Cell>);
            }
            return (<Cells access={!!section.header.access}>{rows}</Cells>);
        });

        var table = [];
        for (var i = 0; i < section_titles.length; i++) {
            table.push(section_titles[i]);
            table.push(section_rows[i]);
        }

        var components = [];
        if (this.props.headerView) {
            components.push(this.props.headerView)
        }
        if (table.length > 0) {
            components.push(table);
        }
        if (this.props.footerView) {
            components.push(this.props.footerView)
        }

        if (components.length === 0) {
            return (null);
        } else {
            return (

                    {components}

            );
        }
    }
}

/*
 sections =  [
    {
        header:{title:'', access:true ...},
        rows:[{title, subTitle, jumpUrl}, {}]
    }
 ]
 其中access只能按section配置, 不能指定单独cell
 */
ListView.defaultProps = {
    sections: []
};

ListView.propTypes = {
    sections: React.PropTypes.array,
    headerView: React.PropTypes.element,
    footerView: React.PropTypes.element,
};
<|endoftext|>


<commit_before>define(['logManager', 'js/NodePropertyNames'], function (logManager, nodePropertyNames) {
  "use strict";

  var logger, client;

  var ObjectConstraintManager = function(client_) {
    logManager.setLogLevel(logManager.logLevels.INFO);
    logger = logManager.create('ObjectConstraintManager');
    client = client_;
  };

  ObjectConstraintManager.prototype.validateAll = function(id) {

    validate(id);

    function validate (id) {

      // Validate the node itself..
      var node = client.getNode(id);
      var node_name = node.getAttribute('name');
      logger.info('Validating node: ' + id + ' ' + node_name);

      var constraints = [];
      var constraint_names = node.getConstraintNames();
      constraint_names.forEach(function(constraint_name) {
        var constraint_obj = node.getConstraint(constraint_name);
        constraint_obj.name = constraint_name;
        constraints.push(constraint_obj);
      });

      constraints.forEach(function(constraint) {

        var result = eval("(" + constraint.script + ")(client, node);");

        var msg = '[[ <' + constraint.name + '> of <' + node_name + '> ]]';
        if (result) {
            logger.info('No violation of a constraint: ' +
              msg);
        } else {
            logger.error('!!Violation of a constraint: ' +
              msg + "!!!");
        }

      });

      // var y = function(input, callback) {
      //   //
      //   callback(withsomething);
      // }

      // Call validation for the node's children
      var children = node.getChildrenIds();
      children.forEach(function(child) {
        validate(child);
      });

    }

  };

  return ObjectConstraintManager;
});<commit_msg>update territory functionality is added to Constraint Manager<commit_after>define(['logManager', 'js/NodePropertyNames'], function (logManager, nodePropertyNames) {
  "use strict";

  var self, logger, client;
  var toBeValidated;

  var ObjectConstraintManager = function(client_) {
    self = this;
    logManager.setLogLevel(logManager.logLevels.INFO);
    logger = logManager.create('ObjectConstraintManager');
    client = client_;
  };

  ObjectConstraintManager.prototype.validateAll = function(id) {
    toBeValidated = id;
    var territoryId = client.addUI(this, true);
    client.updateTerritory(territoryId, {root: {'children': Number.MAX_VALUE}});
  };

  ObjectConstraintManager.prototype.validate = function(id) {
    // Validate the node itself..
    var node = client.getNode(id);
    var node_name = node.getAttribute('name');
    logger.info('Validating node: ' + id + ' ' + node_name);

    var constraints = [];
    var constraint_names = node.getConstraintNames();
    constraint_names.forEach(function(constraint_name) {
      var constraint_obj = node.getConstraint(constraint_name);
      constraint_obj.name = constraint_name;
      constraints.push(constraint_obj);
    });

    constraints.forEach(function(constraint) {

      var result = eval("(" + constraint.script + ")(client, node);");

      var msg = '[[ <' + constraint.name + '> of <' + node_name + '> ]]';
      if (result) {
          logger.info('No violation of a constraint: ' +
            msg);
      } else {
          logger.error('!!Violation of a constraint: ' +
            msg + "!!!");
      }

    });

    // var y = function(input, callback) {
    //   //
    //   callback(withsomething);
    // }

    // Call validation for the node's children
    var children = node.getChildrenIds();
    children.forEach(function(child) {
      self.validate(child);
    });
  }

  ObjectConstraintManager.prototype.onOneEvent = function (events) {
    logger.info('Territory is updated, validation begins');
    self.validate(toBeValidated);
  }

  return ObjectConstraintManager;
});<|endoftext|>


<commit_before>import chalk from 'chalk';
import Rx from 'rxjs';

import { tryJSON } from '../util/fetchUtils';
import { applyAuthState, removeAuthState } from '../util/authUtils';

/**
 * @module requestAuthPlugin
 */

function verifyAuth(auth) {
	const keys = Object.keys(auth);
	if (!keys.length) {
		const errorMessage = 'No auth token(s) provided';
		console.error(
			chalk.red(errorMessage),
			': application can not fetch data.',
			'You might be able to recover by clearing cookies and refreshing'
		);
		throw new Error(errorMessage);
	}
	return auth;
}

const handleLogout = request => {
	request.log(['info', 'auth'], 'Logout received, clearing cookies to re-authenticate');
	return removeAuthState(['oauth_token', 'refresh_token'], request, request.authorize.reply);
};

/**
 * Ensure that the passed-in Request contains a valid Oauth token
 *
 * If the Request already has a valid oauth token, it is returned unchanged,
 * otherwise the request is parsed for more info and a new token is set
 *
 * @param {Observable} auth$ a function that takes a request and emits new auth
 *   data
 * @param {Request} request Hapi request to modify with auth token (if necessary)
 * @return {Observable} Observable that emits the request with auth applied
 */
export const requestAuthorizer = auth$ => request => {
	// logout is accomplished exclusively through a `logout` querystring value
	if ('logout' in request.query) {
		handleLogout(request);
	}

	// always need oauth_token, even if it's an anonymous (pre-reg) token
	// This is 'deferred' because we don't want to start fetching the token
	// before we know that it's needed
	const request$ = Rx.Observable.of(request);
	const authType = request.state.oauth_token && 'cookie';

	request.log(['info', 'auth'], 'Checking for oauth_token in request');
	return Rx.Observable.if(
		() => authType,
		request$
			.do(() => request.log(['info', 'auth'], `Request contains auth token (${authType})`)),
		request$
			.do(() => request.log(['info', 'auth'], 'Request does not contain auth token'))
			.flatMap(request => auth$(request).do(applyAuthState(request, request.authorize.reply)).map(() => request))
	);
};

/**
 * Get an anonymous code from the API that can be used to generate an oauth
 * access token
 *
 * @param {Object} config { OAUTH_AUTH_URL, oauth }
 * @param {String} redirect_uri Return url after anonymous grant
 */
export function getAnonymousCode$({ API_TIMEOUT=5000, OAUTH_AUTH_URL, oauth }, redirect_uri) {
	if (!oauth.key) {
		throw new ReferenceError('OAuth consumer key is required');
	}

	const authParams = new URLSearchParams();
	authParams.append('response_type', 'anonymous_code');
	authParams.append('client_id', oauth.key);
	authParams.append('redirect_uri', redirect_uri);
	const authURL = `${OAUTH_AUTH_URL}?${authParams}`;
	const requestOpts = {
		method: 'GET',
		headers: {
			Accept: 'application/json'
		},
	};

	return Rx.Observable.defer(() => {
		console.log(`Fetching anonymous auth code from ${OAUTH_AUTH_URL}`);
		return Rx.Observable.fromPromise(fetch(authURL, requestOpts))
			.timeout(API_TIMEOUT)
			.flatMap(tryJSON(OAUTH_AUTH_URL))
			.map(({ code }) => ({
				grant_type: 'anonymous_code',
				token: code
			}));
	});
}

/**
 * Curry the config to generate a function that receives a grant type and grant
 * token that can be used to generate an oauth access token from the API
 * @param {Object} config object containing the oauth secret and key
 * @param {String} redirect_uri Return url after anonymous grant
 * @param {Object} headers Hapi request headers for anonymous user request
 * @return {Object} the JSON-parsed response from the authorize endpoint
 *   - contains 'access_token', 'refresh_token'
 */
export const getAccessToken$ = ({ API_TIMEOUT=5000, OAUTH_ACCESS_URL, oauth }, redirect_uri) => {
	if (!oauth.key) {
		throw new ReferenceError('OAuth consumer key is required');
	}
	if (!oauth.secret) {
		throw new ReferenceError('OAuth consumer secret is required');
	}
	const params = {
		client_id: oauth.key,
		client_secret: oauth.secret,
		redirect_uri
	};
	return headers => {
		const requestOpts = {
			method: 'POST',
			headers: {
				Cookie: headers.cookie,
				Accept: headers.accept,
				'Accept-Language': headers['accept-language'],
				'Cache-Control': headers['cache-control']
			},
		};
		const accessParams = Object.keys(params)
			.reduce((accessParams, key) => {
				accessParams.append(key, params[key]);
				return accessParams;
			}, new URLSearchParams());

		return ({ grant_type, token }) => {

			if (!token) {
				// programmer error or catastrophic auth failure - throw exception
				throw new ReferenceError('No grant token provided - cannot obtain access token');
			}

			accessParams.append('grant_type', grant_type);
			if (grant_type === 'anonymous_code') {
				console.log(`Fetching anonymous access_token from ${OAUTH_ACCESS_URL}`);
				accessParams.append('code', token);
			}
			if (grant_type === 'refresh_token') {
				console.log(`Refreshing access_token from ${OAUTH_ACCESS_URL}`);
				accessParams.append('refresh_token', token);
			}

			const url = `${OAUTH_ACCESS_URL}?${accessParams}`;

			return Rx.Observable.fromPromise(fetch(url, requestOpts))
				.timeout(API_TIMEOUT)
				.flatMap(tryJSON(OAUTH_ACCESS_URL));
		};
	};
};

const refreshToken$ = refresh_token => Rx.Observable.of({
	grant_type: 'refresh_token',
	token: refresh_token
});

/**
 * Curry a function that will get a new auth token for a passed-in request.
 * For an anonymous auth, the request header information is used to determine
 * the location and language of the anonymous member
 *
 * @param {Object} config { OAUTH_AUTH_URL, OAUTH_ACCESS_URL, oauth }
 * @param {Object} request the Hapi request that needs to be authorized
 */
export const requestAuth$ = config => {
	const redirect_uri = 'http://www.meetup.com/';  // required param set in oauth consumer config
	const anonymousCode$ = getAnonymousCode$(config, redirect_uri);
	const accessToken$ = getAccessToken$(config, redirect_uri);

	// if the request has a refresh_token, use it. Otherwise, get a new anonymous access token
	return ({ headers, state: { refresh_token} }) =>
		Rx.Observable.if(
			() => refresh_token,
			refreshToken$(refresh_token),
			anonymousCode$
		)
		.flatMap(accessToken$(headers))
		.do(verifyAuth);
};

export const authenticate = (request, reply) => {
	request.log(['info', 'auth'], 'Authenticating request');
	console.log(`\nreceived headers:\n${JSON.stringify(request.headers, null, 2)}\n`);
	return request.authorize()
		.do(request => {
			request.log(['info', 'auth'], 'Request authenticated');
		})
		.subscribe(({ state: { oauth_token } }) => {
			const credentials = oauth_token;
			reply.continue({ credentials, artifacts: credentials });
		});
};

/**
 * Request authorizing scheme
 *
 * 1. add a `.authorize` method to the request
 * 2. assign a reference to the reply interface on request.authorize
 * 3. add an anonymous-user-JSON-generating route (for app logout)
 * 4. return the authentication function, which ensures that all requests have
 * valid auth credentials (anonymous or logged in)
 */
export const oauthScheme = (server, options) => {
	// create a single requestAuth$ stream that can be used by any route
	const auth$ = requestAuth$(options);
	// create a single stream for modifying an arbitrary request with anonymous auth
	const authorizeRequest$ = requestAuthorizer(auth$);

	server.decorate(
		'request',
		'authorize',
		request => () => authorizeRequest$(request),
		{ apply: true }
	);

	server.ext('onPreAuth', (request, reply) => {
		// Used for setting and unsetting state, not for replying to request
		request.authorize.reply = reply;

		return reply.continue();
	});

	return { authenticate };
};
/**
 * This plugin does two things.
 *
 * 1. Adds an 'authorize' interface on the Hapi `request`, which ensures that
 * the request has an oauth_token cookie - it provides an anonymous token when
 * none is provided in the request, and refreshes a token that has expired
 * 2. Adds a new route that returns the auth JSON containing the new oauth_token
 * (configurable, defaults to '/auth')
 *
 * {@link http://hapijs.com/tutorials/plugins}
 */
export default function register(server, options, next) {
	server.auth.scheme('oauth', oauthScheme);
	next();
}
register.attributes = {
	name: 'requestAuth',
	version: '1.0.0',
};

<commit_msg>remove extra log<commit_after>import chalk from 'chalk';
import Rx from 'rxjs';

import { tryJSON } from '../util/fetchUtils';
import { applyAuthState, removeAuthState } from '../util/authUtils';

/**
 * @module requestAuthPlugin
 */

function verifyAuth(auth) {
	const keys = Object.keys(auth);
	if (!keys.length) {
		const errorMessage = 'No auth token(s) provided';
		console.error(
			chalk.red(errorMessage),
			': application can not fetch data.',
			'You might be able to recover by clearing cookies and refreshing'
		);
		throw new Error(errorMessage);
	}
	return auth;
}

const handleLogout = request => {
	request.log(['info', 'auth'], 'Logout received, clearing cookies to re-authenticate');
	return removeAuthState(['oauth_token', 'refresh_token'], request, request.authorize.reply);
};

/**
 * Ensure that the passed-in Request contains a valid Oauth token
 *
 * If the Request already has a valid oauth token, it is returned unchanged,
 * otherwise the request is parsed for more info and a new token is set
 *
 * @param {Observable} auth$ a function that takes a request and emits new auth
 *   data
 * @param {Request} request Hapi request to modify with auth token (if necessary)
 * @return {Observable} Observable that emits the request with auth applied
 */
export const requestAuthorizer = auth$ => request => {
	// logout is accomplished exclusively through a `logout` querystring value
	if ('logout' in request.query) {
		handleLogout(request);
	}

	// always need oauth_token, even if it's an anonymous (pre-reg) token
	// This is 'deferred' because we don't want to start fetching the token
	// before we know that it's needed
	const request$ = Rx.Observable.of(request);
	const authType = request.state.oauth_token && 'cookie';

	request.log(['info', 'auth'], 'Checking for oauth_token in request');
	return Rx.Observable.if(
		() => authType,
		request$
			.do(() => request.log(['info', 'auth'], `Request contains auth token (${authType})`)),
		request$
			.do(() => request.log(['info', 'auth'], 'Request does not contain auth token'))
			.flatMap(request => auth$(request).do(applyAuthState(request, request.authorize.reply)).map(() => request))
	);
};

/**
 * Get an anonymous code from the API that can be used to generate an oauth
 * access token
 *
 * @param {Object} config { OAUTH_AUTH_URL, oauth }
 * @param {String} redirect_uri Return url after anonymous grant
 */
export function getAnonymousCode$({ API_TIMEOUT=5000, OAUTH_AUTH_URL, oauth }, redirect_uri) {
	if (!oauth.key) {
		throw new ReferenceError('OAuth consumer key is required');
	}

	const authParams = new URLSearchParams();
	authParams.append('response_type', 'anonymous_code');
	authParams.append('client_id', oauth.key);
	authParams.append('redirect_uri', redirect_uri);
	const authURL = `${OAUTH_AUTH_URL}?${authParams}`;
	const requestOpts = {
		method: 'GET',
		headers: {
			Accept: 'application/json'
		},
	};

	return Rx.Observable.defer(() => {
		console.log(`Fetching anonymous auth code from ${OAUTH_AUTH_URL}`);
		return Rx.Observable.fromPromise(fetch(authURL, requestOpts))
			.timeout(API_TIMEOUT)
			.flatMap(tryJSON(OAUTH_AUTH_URL))
			.map(({ code }) => ({
				grant_type: 'anonymous_code',
				token: code
			}));
	});
}

/**
 * Curry the config to generate a function that receives a grant type and grant
 * token that can be used to generate an oauth access token from the API
 * @param {Object} config object containing the oauth secret and key
 * @param {String} redirect_uri Return url after anonymous grant
 * @param {Object} headers Hapi request headers for anonymous user request
 * @return {Object} the JSON-parsed response from the authorize endpoint
 *   - contains 'access_token', 'refresh_token'
 */
export const getAccessToken$ = ({ API_TIMEOUT=5000, OAUTH_ACCESS_URL, oauth }, redirect_uri) => {
	if (!oauth.key) {
		throw new ReferenceError('OAuth consumer key is required');
	}
	if (!oauth.secret) {
		throw new ReferenceError('OAuth consumer secret is required');
	}
	const params = {
		client_id: oauth.key,
		client_secret: oauth.secret,
		redirect_uri
	};
	return headers => {
		const requestOpts = {
			method: 'POST',
			headers: {
				Cookie: headers.cookie,
				Accept: headers.accept,
				'Accept-Language': headers['accept-language'],
				'Cache-Control': headers['cache-control']
			},
		};
		const accessParams = Object.keys(params)
			.reduce((accessParams, key) => {
				accessParams.append(key, params[key]);
				return accessParams;
			}, new URLSearchParams());

		return ({ grant_type, token }) => {

			if (!token) {
				// programmer error or catastrophic auth failure - throw exception
				throw new ReferenceError('No grant token provided - cannot obtain access token');
			}

			accessParams.append('grant_type', grant_type);
			if (grant_type === 'anonymous_code') {
				console.log(`Fetching anonymous access_token from ${OAUTH_ACCESS_URL}`);
				accessParams.append('code', token);
			}
			if (grant_type === 'refresh_token') {
				console.log(`Refreshing access_token from ${OAUTH_ACCESS_URL}`);
				accessParams.append('refresh_token', token);
			}

			const url = `${OAUTH_ACCESS_URL}?${accessParams}`;

			return Rx.Observable.fromPromise(fetch(url, requestOpts))
				.timeout(API_TIMEOUT)
				.flatMap(tryJSON(OAUTH_ACCESS_URL));
		};
	};
};

const refreshToken$ = refresh_token => Rx.Observable.of({
	grant_type: 'refresh_token',
	token: refresh_token
});

/**
 * Curry a function that will get a new auth token for a passed-in request.
 * For an anonymous auth, the request header information is used to determine
 * the location and language of the anonymous member
 *
 * @param {Object} config { OAUTH_AUTH_URL, OAUTH_ACCESS_URL, oauth }
 * @param {Object} request the Hapi request that needs to be authorized
 */
export const requestAuth$ = config => {
	const redirect_uri = 'http://www.meetup.com/';  // required param set in oauth consumer config
	const anonymousCode$ = getAnonymousCode$(config, redirect_uri);
	const accessToken$ = getAccessToken$(config, redirect_uri);

	// if the request has a refresh_token, use it. Otherwise, get a new anonymous access token
	return ({ headers, state: { refresh_token} }) =>
		Rx.Observable.if(
			() => refresh_token,
			refreshToken$(refresh_token),
			anonymousCode$
		)
		.flatMap(accessToken$(headers))
		.do(verifyAuth);
};

export const authenticate = (request, reply) => {
	request.log(['info', 'auth'], 'Authenticating request');
	return request.authorize()
		.do(request => {
			request.log(['info', 'auth'], 'Request authenticated');
		})
		.subscribe(({ state: { oauth_token } }) => {
			const credentials = oauth_token;
			reply.continue({ credentials, artifacts: credentials });
		});
};

/**
 * Request authorizing scheme
 *
 * 1. add a `.authorize` method to the request
 * 2. assign a reference to the reply interface on request.authorize
 * 3. add an anonymous-user-JSON-generating route (for app logout)
 * 4. return the authentication function, which ensures that all requests have
 * valid auth credentials (anonymous or logged in)
 */
export const oauthScheme = (server, options) => {
	// create a single requestAuth$ stream that can be used by any route
	const auth$ = requestAuth$(options);
	// create a single stream for modifying an arbitrary request with anonymous auth
	const authorizeRequest$ = requestAuthorizer(auth$);

	server.decorate(
		'request',
		'authorize',
		request => () => authorizeRequest$(request),
		{ apply: true }
	);

	server.ext('onPreAuth', (request, reply) => {
		// Used for setting and unsetting state, not for replying to request
		request.authorize.reply = reply;

		return reply.continue();
	});

	return { authenticate };
};
/**
 * This plugin does two things.
 *
 * 1. Adds an 'authorize' interface on the Hapi `request`, which ensures that
 * the request has an oauth_token cookie - it provides an anonymous token when
 * none is provided in the request, and refreshes a token that has expired
 * 2. Adds a new route that returns the auth JSON containing the new oauth_token
 * (configurable, defaults to '/auth')
 *
 * {@link http://hapijs.com/tutorials/plugins}
 */
export default function register(server, options, next) {
	server.auth.scheme('oauth', oauthScheme);
	next();
}
register.attributes = {
	name: 'requestAuth',
	version: '1.0.0',
};

<|endoftext|>


<commit_before>"use strict";

angular.module("hikeio").
	factory("search", ["$http", "$log", "$q", "$rootScope", "navigation", "persistentStorage", "resourceCache", function($http, $log, $q, $rootScope, navigation, persistentStorage, resourceCache) {

		var SearchService = function() {
		};

		var getBestGeocodeResult = function(data) {
			// Loop over results until we find a city, state, province, country, etc.
			// results sholud be ordered by relevance
			if (data.status === "OK" && data.results && data.results.length > 0) {
				var results = data.results;
				for (var i = 0; i < results.length; i++) {
					var result = results[i];
					var types = result.types;
					for (var j = 0; j < types.length; j++) {
						// https://developers.google.com/maps/documentation/geocoding/#Types
						var type = types[j];
						if (type === "political" ||
							type === "country" ||
							type === "administrative_area_level_1" ||
							type === "administrative_area_level_2" ||
							type === "administrative_area_level_3" ||
							type === "locality" ||
							type === "sublocality" ||
							type === "sublocality_level_1" ||
							type === "postal_code" ||
							type === "park" ||
							type === "natural_feature") {
							return result;
						}
					}
				}
			}
		};

		var getMapViewportFromGeocodeResult = function(result) {
			var viewport = result.geometry.viewport;
			return {
				northEast: {
					latitude: viewport.northeast.lat,
					longitude: viewport.northeast.lng
				},
				southWest: {
					latitude: viewport.southwest.lat,
					longitude: viewport.southwest.lng
				}
			};
		};

		var cleanupFormattedAddress = function(address) {
			// Remove zip code
			var result = address.replace(/ \d{5}/, "");

			// Remove USA, unless there is another locality in the string. We want it to say Washington, USA. But not Seattle, WA, USA.
			if (result.split(",").length !== 2) {
				result = result.replace(/, USA/, "");
			}
			return result;
		};

		var searchByLocation = function(query) {
			// This request should be automatically biased based on where the request is coming from
			return $http({method: "GET", url: "http://maps.googleapis.com/maps/api/geocode/json", params: { address: query, sensor: false }, cache: resourceCache}).
				success(function(data) {
					// First make sure that what we got back is actually useful, we don't want to bring up the map to a specific address.
					var result = getBestGeocodeResult(data);
					if (result) {
						var viewport = getMapViewportFromGeocodeResult(result);
						persistentStorage.set("/map", { viewport: viewport, formattedLocationString: cleanupFormattedAddress(result.formatted_address)});
						if (navigation.onMap()) {
							$rootScope.$broadcast("resetMapViewport", viewport);
						} else {
							navigation.toMap();
						}
					}
				}).error(function(data, status, headers, config) {
					$log.error(data, status, headers, config);
				});
		};

		var searchByName = function(query) {
			return $http({method: "GET", url: "/api/v1/hikes/search", params: { q: query }, cache: resourceCache}).
				success(function(data, status, headers, config) {
					if (data.length === 1 && data[0].relevance > 0.7) {
						var hike = data[0].hike;
						resourceCache.put("/api/v1/hikes/" + hike.string_id, jQuery.extend(true, {}, hike));
						navigation.toEntry(hike.string_id);
					} else if (data.length > 0) {
						navigation.toSearch(query);
					}
				}).
				error(function(data, status, headers, config) {
					$log.error(data, status, headers, config);
				});
		};

		SearchService.prototype.search = function(query) {
			var deferred = $q.defer();
			var promise = deferred.promise;

			// Secret feature, allow user to force searching by location if they prepend their query with a !
			if (query[0] === "!") {
				searchByLocation(query.slice(1)).then(function() {
					deferred.resolve();
				});
				return promise;
			}

			// Otherwise, first check to see if there is a hike with this name
			searchByName(query).then(function(result) {
				if (result.data.length === 0) {
					// Unable to find by name, try by location
					searchByLocation(query).then(function(result) {
						if (!getBestGeocodeResult(result.data)) {
							navigation.toSearch(query);
						}
						deferred.resolve();
					});
				} else {
					deferred.resolve();
				}
			});
			return promise;
		};

		return new SearchService();
	}]);
<commit_msg>Search by location if searching by name yields poor results.<commit_after>"use strict";

angular.module("hikeio").
	factory("search", ["$http", "$log", "$q", "$rootScope", "navigation", "persistentStorage", "resourceCache", function($http, $log, $q, $rootScope, navigation, persistentStorage, resourceCache) {

		var SearchService = function() {
		};

		var getBestGeocodeResult = function(data) {
			// Loop over results until we find a city, state, province, country, etc.
			// results sholud be ordered by relevance
			if (data.status === "OK" && data.results && data.results.length > 0) {
				var results = data.results;
				for (var i = 0; i < results.length; i++) {
					var result = results[i];
					var types = result.types;
					for (var j = 0; j < types.length; j++) {
						// https://developers.google.com/maps/documentation/geocoding/#Types
						var type = types[j];
						if (type === "political" ||
							type === "country" ||
							type === "administrative_area_level_1" ||
							type === "administrative_area_level_2" ||
							type === "administrative_area_level_3" ||
							type === "locality" ||
							type === "sublocality" ||
							type === "sublocality_level_1" ||
							type === "postal_code" ||
							type === "park" ||
							type === "natural_feature") {
							return result;
						}
					}
				}
			}
		};

		var getMapViewportFromGeocodeResult = function(result) {
			var viewport = result.geometry.viewport;
			return {
				northEast: {
					latitude: viewport.northeast.lat,
					longitude: viewport.northeast.lng
				},
				southWest: {
					latitude: viewport.southwest.lat,
					longitude: viewport.southwest.lng
				}
			};
		};

		var cleanupFormattedAddress = function(address) {
			// Remove zip code
			var result = address.replace(/ \d{5}/, "");

			// Remove USA, unless there is another locality in the string. We want it to say Washington, USA. But not Seattle, WA, USA.
			if (result.split(",").length !== 2) {
				result = result.replace(/, USA/, "");
			}
			return result;
		};

		var searchByLocation = function(query) {
			// This request should be automatically biased based on where the request is coming from
			return $http({method: "GET", url: "http://maps.googleapis.com/maps/api/geocode/json", params: { address: query, sensor: false }, cache: resourceCache}).
				success(function(data) {
					// First make sure that what we got back is actually useful, we don't want to bring up the map to a specific address.
					var result = getBestGeocodeResult(data);
					if (result) {
						var viewport = getMapViewportFromGeocodeResult(result);
						persistentStorage.set("/map", { viewport: viewport, formattedLocationString: cleanupFormattedAddress(result.formatted_address)});
						if (navigation.onMap()) {
							$rootScope.$broadcast("resetMapViewport", viewport);
						} else {
							navigation.toMap();
						}
					}
				}).error(function(data, status, headers, config) {
					$log.error(data, status, headers, config);
				});
		};

		var searchByName = function(query) {
			return $http({method: "GET", url: "/api/v1/hikes/search", params: { q: query }, cache: resourceCache}).
				success(function(data, status, headers, config) {
					var relevanceThreshold = 0.7;
					if (data.length === 1 && data[0].relevance > relevanceThreshold) {
						var hike = data[0].hike;
						resourceCache.put("/api/v1/hikes/" + hike.string_id, jQuery.extend(true, {}, hike));
						navigation.toEntry(hike.string_id);
					} else {
						// If any of the results are of high enough relevance, then we want to see those results first
						// If they're low quality matches, try searching by location.
						for (var i = 0; i < data.length) {
							var result = data[i];
							if (data.relevance > relevanceThreshold) {
								navigation.toSearch(query);
								break;
							}
						}
					}
				}).
				error(function(data, status, headers, config) {
					$log.error(data, status, headers, config);
				});
		};

		SearchService.prototype.search = function(query) {
			var deferred = $q.defer();
			var promise = deferred.promise;

			// Secret feature, allow user to force searching by location if they prepend their query with a !
			if (query[0] === "!") {
				searchByLocation(query.slice(1)).then(function() {
					deferred.resolve();
				});
				return promise;
			}

			// Otherwise, first check to see if there is a hike with this name
			searchByName(query).then(function(result) {
				if (result.data.length !== 0) {
					// Unable to find good match name, try by location
					searchByLocation(query).then(function(result) {
						if (!getBestGeocodeResult(result.data)) {
							navigation.toSearch(query);
						}
						deferred.resolve();
					});
				} else {
					deferred.resolve();
				}
			});
			return promise;
		};

		return new SearchService();
	}]);
<|endoftext|>


<commit_before>import { push } from 'react-router-redux';
import isEmpty from 'lodash/isEmpty';
import { CHANGE_LISTING_FIELD, UPLOAD_LISTING_IMAGE, ADD_LISTING_FAILURE, ADD_LISTING_SUCCESS, GET_LISTINGS_SUCCESS, DELETE_IMAGE, FETCHING_LISTINGS, POST_LISTING_SUCCESS, ADD_LISTING_FORM_ERROR, CLEAR_ERRORS } from '../constants';
import { postListing, deleteListing, getUserListings } from './api';
import validateListing from '../components/helpers/validateListing';

const urlRegex = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi;

export const changeListingField = (field, value) =>
  ({
    type: CHANGE_LISTING_FIELD,
    field,
    value,
  });

export const uploadListingImage = (value) => {
  if (value.img_path.match(urlRegex)) {
    return ({
      type: UPLOAD_LISTING_IMAGE,
      value,
    });
  }
  return ({
    type: ADD_LISTING_FORM_ERROR,
    error: 'image',
    message: 'Image URL is invalid',
  });
};

export const clearErrors = () =>
  ({
    type: CLEAR_ERRORS,
  });

const addListingError = err =>
  ({
    type: ADD_LISTING_FAILURE,
    err,
  });

const getListingSuccess = payload =>
  ({
    type: GET_LISTINGS_SUCCESS,
    payload,
  });

const startFetchUserListings = () =>
  ({
    type: FETCHING_LISTINGS,
  });

const postListingSuccess = () =>
  ({
    type: POST_LISTING_SUCCESS,
  });

export const fetchUserListings = id =>
  (dispatch) => {
    dispatch(startFetchUserListings());
    getUserListings(id)
      .then((res) => {
        res.json()
          .then((data) => {
            dispatch(getListingSuccess(data));
          });
      });
  };

export const uploadListing = data =>
  (dispatch, getState) => {
    validateListing(data.data, dispatch);
    if (isEmpty(getState().listing.formErrors)) {
      postListing(data)
        .then((res) => {
          res.json()
            .then((payload) => {
              if (payload.error) {
                dispatch(addListingError(payload.error));
              } else {
                dispatch(postListingSuccess());
                dispatch(fetchUserListings(data.id));
                dispatch(push('/dashboard'));
              }
            });
        });
    }
  };

export const removeListing = (listingId, userId) =>
  (dispatch) => {
    deleteListing(listingId)
      .then(() => {
        dispatch(fetchUserListings(userId));
        dispatch(push('/dashboard'));
      });
  };

export const deleteImage = index =>
  ({
    type: DELETE_IMAGE,
    index,
  });

export const addListingFormError = (error, message) =>
  ({
    type: ADD_LISTING_FORM_ERROR,
    error,
    message,
  });
<commit_msg>fix hanging listings bug<commit_after>import { push } from 'react-router-redux';
import isEmpty from 'lodash/isEmpty';
import { CHANGE_LISTING_FIELD, UPLOAD_LISTING_IMAGE, ADD_LISTING_FAILURE, ADD_LISTING_SUCCESS, GET_LISTINGS_SUCCESS, DELETE_IMAGE, FETCHING_LISTINGS, POST_LISTING_SUCCESS, ADD_LISTING_FORM_ERROR, CLEAR_ERRORS } from '../constants';
import { postListing, deleteListing, getUserListings } from './api';
import validateListing from '../components/helpers/validateListing';
import { getAllListings } from './allListingActions';

const urlRegex = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi;

export const changeListingField = (field, value) =>
  ({
    type: CHANGE_LISTING_FIELD,
    field,
    value,
  });

export const uploadListingImage = (value) => {
  if (value.img_path.match(urlRegex)) {
    return ({
      type: UPLOAD_LISTING_IMAGE,
      value,
    });
  }
  return ({
    type: ADD_LISTING_FORM_ERROR,
    error: 'image',
    message: 'Image URL is invalid',
  });
};

export const clearErrors = () =>
  ({
    type: CLEAR_ERRORS,
  });

const addListingError = err =>
  ({
    type: ADD_LISTING_FAILURE,
    err,
  });

const getListingSuccess = payload =>
  ({
    type: GET_LISTINGS_SUCCESS,
    payload,
  });

const startFetchUserListings = () =>
  ({
    type: FETCHING_LISTINGS,
  });

const postListingSuccess = () =>
  ({
    type: POST_LISTING_SUCCESS,
  });

export const fetchUserListings = id =>
  (dispatch) => {
    dispatch(startFetchUserListings());
    getUserListings(id)
      .then((res) => {
        res.json()
          .then((data) => {
            dispatch(getListingSuccess(data));
          });
      });
  };

export const uploadListing = data =>
  (dispatch, getState) => {
    validateListing(data.data, dispatch);
    if (isEmpty(getState().listing.formErrors)) {
      postListing(data)
        .then((res) => {
          res.json()
            .then((payload) => {
              if (payload.error) {
                dispatch(addListingError(payload.error));
              } else {
                dispatch(postListingSuccess());
                dispatch(fetchUserListings(data.id));
                dispatch(push('/dashboard'));
                dispatch(getAllListings());
              }
            });
        });
    }
  };

export const removeListing = (listingId, userId) =>
  (dispatch) => {
    deleteListing(listingId)
      .then(() => {
        dispatch(fetchUserListings(userId));
        dispatch(push('/dashboard'));
      });
  };

export const deleteImage = index =>
  ({
    type: DELETE_IMAGE,
    index,
  });

export const addListingFormError = (error, message) =>
  ({
    type: ADD_LISTING_FORM_ERROR,
    error,
    message,
  });
<|endoftext|>


<commit_before>module.exports = [
  'contain',
  'cover',
  'stretch',
  'repeat',
  'center',
];
<commit_msg>Use Platform to determine extra iOS propTypes<commit_after>import Platfrom from '../plugins/Platform';

const resizePropTypes = [
  'contain',
  'cover',
  'stretch',
];

if (Platfrom.OS === 'ios') {
  resizePropTypes.push('repeat', 'center');
}

module.exports = resizePropTypes;
<|endoftext|>


<commit_before>angular.module('resources.users', []);
angular.module('resources.users').factory('Users', function ($resource) {

  var userResource = $resource('users');
  userResource.prototype.getFullName = function () {
    return this.lastName + " " + this.firstName + " (" + this.email + ")";
  };

  userResource.prototype.$saveOrUpdate = function (onSave, onSave, onError, onError) {
      $resource('/admin/users/:userId').save(this, function(note) {
          onSave(note);
      });
  };

  userResource.prototype.$id = function () {
    return this._id;
  };

  userResource.prototype.$remove = function (sucfun,errfun) {
      try{
          $resource('/admin/users/:userId').delete( { userId: this._id } );
          sucfun();
      }catch(e){
          errfun();
      }

  };

  userResource.forUser = function (userId) {
    return userResource.query({userId: userId });
  };

  userResource.all = function (params) {
      return userResource.query(params);
  };

  userResource.getById = function (userId) {
    return $resource('/admin/users/:userId').get( { userId: userId } );
  };

  userResource.byEmail = function (email) {
    return $resource('/users/:email').get( { email: email } );
  };

  return userResource;

});
<commit_msg>update user<commit_after>angular.module('resources.users', []);
angular.module('resources.users').factory('Users', function ($resource) {

  var userResource = $resource('users');
  userResource.prototype.getFullName = function () {
    return this.lastName + " " + this.firstName + " (" + this.email + ")";
  };

  userResource.prototype.$saveOrUpdate = function (onSave, onSave, onError, onError) {
      $resource('/admin/users/:userId').save(this, function(note) {
          onSave(note);
      });
  };

  userResource.prototype.$id = function () {
    return this._id;
  };

  userResource.prototype.$remove = function (sucfun,errfun) {
      try{
          $resource('/admin/users/:userId').delete( { userId: this._id } );
          sucfun();
      }catch(e){
          errfun();
      }

  };

  userResource.forUser = function (userId) {
    return userResource.query({userId: userId });
  };

  userResource.all = function (params) {
      return userResource.query(params);
  };

  userResource.getById = function (userId) {
    var user = $resource('/admin/users/:userId').get( { userId: userId } );
    user.__proto__= this.prototype;
    return user;
  };

  userResource.byEmail = function (email) {
    return $resource('/users/:email').get( { email: email } );
  };

  return userResource;

});
<|endoftext|>


<commit_before>'use strict';

import {workToJSON} from '../requestTypeIdentifier.js';

export default (params, context) => {
  if (!params.q) {
    return new Promise({statusCode: 400,
                    error: 'missing q parameter'});
  }

  let agency = context.opensearch.agency;
  let profile = context.opensearch.profile;
  let q = params.q.replace(/</g, '<');
  let sort = (params.sort || '').replace(/</g, '<');
  let offset = 1 + (parseInt(params.offset, 10) || 0);
  let limit = parseInt(params.limit, 10) || 10;

  let soap = `<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://oss.dbc.dk/ns/opensearch">
  <SOAP-ENV:Body>
    <ns1:searchRequest>
      <ns1:query>${q}</ns1:query>
      <ns1:agency>${agency}</ns1:agency>
      <ns1:profile>${profile}</ns1:profile>
      <ns1:start>${offset}</ns1:start>
      <ns1:stepValue>${limit}</ns1:stepValue>
      ${sort?'<ns1:sort>' + sort + '</ns1:sort>' : ''}
      <ns1:collectionType>work-1</ns1:collectionType>
      <ns1:allObjects>1</ns1:allObjects>
      <ns1:objectFormat>briefDisplay</ns1:objectFormat>
      <ns1:objectFormat>dkabm</ns1:objectFormat>
      <ns1:outputType>json</ns1:outputType>
    </ns1:searchRequest>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
`;

  return context.call('opensearch', soap).then(body => {
    body = JSON.parse(body).searchResponse.result;
    // let more = body.more.$; // this could be used for paging info later
    let searchResult = body.searchResult;
    let result = [];
    searchResult.forEach(o => { // eslint-disable-line no-loop-func
      let collection = o.collection.object.map(obj => obj.identifier.$);
      let dkabm = o.collection.object[0].record;
      dkabm = workToJSON(dkabm);
      let briefDisplays =
        o.formattedCollection.briefDisplay.manifestation.map(briefDisplay => {
          delete briefDisplay.fedoraPid;
          return workToJSON(briefDisplay, 'bd');
        });
      // here we would call getObject or moreInfo if needed...
      result.push(Object.assign({
        collection: collection,
        collectionDetails: briefDisplays
      }, dkabm, briefDisplays[0]));
    });
    return {statusCode: 200, data: result};
  });
};
<commit_msg>fixes #197<commit_after>'use strict';

import {workToJSON} from '../requestTypeIdentifier.js';

export default (params, context) => {
  if (!params.q) {
    return new Promise({statusCode: 400,
                    error: 'missing q parameter'});
  }

  let agency = context.opensearch.agency;
  let profile = context.opensearch.profile;
  let q = params.q.replace(/</g, '<');
  let sort = (params.sort || '').replace(/</g, '<');
  let offset = 1 + (parseInt(params.offset, 10) || 0);
  let limit = parseInt(params.limit, 10) || 10;

  let soap = `<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://oss.dbc.dk/ns/opensearch">
  <SOAP-ENV:Body>
    <ns1:searchRequest>
      <ns1:query>${q}</ns1:query>
      <ns1:agency>${agency}</ns1:agency>
      <ns1:profile>${profile}</ns1:profile>
      <ns1:start>${offset}</ns1:start>
      <ns1:stepValue>${limit}</ns1:stepValue>
      ${sort?'<ns1:sort>' + sort + '</ns1:sort>' : ''}
      <ns1:collectionType>work-1</ns1:collectionType>
      <ns1:allObjects>1</ns1:allObjects>
      <ns1:objectFormat>briefDisplay</ns1:objectFormat>
      <ns1:objectFormat>dkabm</ns1:objectFormat>
      <ns1:outputType>json</ns1:outputType>
    </ns1:searchRequest>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
`;

  return context.call('opensearch', soap).then(body => {
    body = JSON.parse(body).searchResponse.result;
    // let more = body.more.$; // this could be used for paging info later
    let searchResult = body.searchResult || [];
    let result = [];
    searchResult.forEach(o => { // eslint-disable-line no-loop-func
      let collection = o.collection.object.map(obj => obj.identifier.$);
      let dkabm = o.collection.object[0].record;
      dkabm = workToJSON(dkabm);
      let briefDisplays =
        o.formattedCollection.briefDisplay.manifestation.map(briefDisplay => {
          delete briefDisplay.fedoraPid;
          return workToJSON(briefDisplay, 'bd');
        });
      // here we would call getObject or moreInfo if needed...
      result.push(Object.assign({
        collection: collection,
        collectionDetails: briefDisplays
      }, dkabm, briefDisplays[0]));
    });
    return {statusCode: 200, data: result};
  });
};
<|endoftext|>


<commit_before>function(doc) {
    var ret = new Document();

    function idx(obj) {
    for (var key in obj) {
        switch (typeof obj[key]) {
        case 'object':
        idx(obj[key]);
        break;
        case 'function':
        break;
        default:
        ret.add(obj[key]);
        break;
        }
    }
    };

    idx(doc);
    return ret;
}<commit_msg>optimized the FTI view<commit_after>function(doc) {
  var ret = new Document();

  if (doc.title) ret.add(doc.title, {"field":"title"})
  if (doc.text) ret.add(doc.title, {"field":"text"})

  return ret;
}<|endoftext|>