<commit_before>'use strict'
let winston = require('winston')
let Error = require('./errors')

// Import controllers
let Token = require('./db').Token
let User = require('./db').User
let rat = require('./controllers/rat').Controller
let Permission = require('./permission')
let rescue = require('./controllers/rescue').Controller
let stream = require('./controllers/stream')
let client = require('./controllers/client').Controller
let user = require('./controllers/user').Controller
let _ = require('underscore')

let controllers = {
  rats: {
    create: [rat.create],
    read: [rat.read],
    update: [rat.update, true],
    delete: [rat.delete, true, 'rat.delete']
  },

  rescues: {
    create: [rescue.create, true],
    read: [rescue.read],
    update: [rescue.update, true],
    delete: [rescue.delete, true, 'rescue.delete']
  },

  users: {
    create: [user.create, true],
    read: [user.read, true, 'user.read'],
    update: [user.update, true],
    delete: [user.delete, true, 'user.delete']
  },

  client: {
    create: [client.create, true, 'self.client.create'],
    read: [client.read, true, 'client.read'],
    update: [client.update, true, 'client.update'],
    delete: [client.delete, true, 'client.delete']
  },

  stream: {
    subscribe: [stream.subscribe, true],
    unsubscribe: [stream.unsubscribe, true]
  }
}

exports.retrieveCaseInsensitiveProperty = function (propertyName, obj) {
  if (!obj) { return null }

  propertyName = propertyName.toLowerCase()

  let caseInsensitivePropertyMap = Object.keys(obj).map(function (prop) {
    return prop.toLowerCase()
  })

  let indexOfProperty = caseInsensitivePropertyMap.indexOf(propertyName)

  if (indexOfProperty !== -1) {
    return obj[Object.keys(obj)[indexOfProperty]]
  }
  return null
}

exports.socket = null

exports.received = function (client, requestString) {
  let action, data, requestMeta, request
  try {
    request = JSON.parse(requestString)

    let requestHasValidAction = false
    let requestHadActionField = false
    action = exports.retrieveCaseInsensitiveProperty('action', request)
    data = exports.retrieveCaseInsensitiveProperty('data', request)
    requestMeta = exports.retrieveCaseInsensitiveProperty('meta', request)
    if (!requestMeta) { requestMeta = {} }
    if (!data) { data = {} }
    requestMeta.action = action

    let query = _.clone(request)

    if (action) {
      requestHadActionField = true
      if (typeof action == 'string') {
        if (action === 'authorization') {
          exports.authorization(query, client, requestMeta)
          return
        }

        requestHasValidAction = action.length > 2 && action.includes(':')
      }
    }

    if (requestHasValidAction === true) {
      let requestSections = action.split(':')
      let namespace = requestSections[0].toLowerCase()

      if (controllers.hasOwnProperty(namespace)) {
        let controller = controllers[namespace]
        let method = requestSections[1].toLowerCase()

        if (method && controller[method]) {
          if (controller[method].length > 1) {
            let isAuthenticated = controller[method][1] === true

            if (isAuthenticated) {
              if (client.user) {
                if (controller[method].length > 2) {
                  Permission.require(controller[method][2]).then(function () {
                    client.websocket = exports
                    callAPIMethod(controller[method][0], data, client, query, action, requestMeta)
                  }).catch(function (error) {
                    exports.error(client, requestMeta, [error])
                  })
                }
                callAPIMethod(controller[method][0], data, client, query, action, requestMeta)
              } else {
                exports.error(client, requestMeta, [Permission.authenticationError()])
              }
              return
            }

            callAPIMethod(controller[method][0], data, client, query, action, requestMeta)
            return
          }
        }
        exports.error(client, {
          action: action
        }, [Error.throw('invalid_parameter', 'action')])
      } else {
        let applicationId = exports.retrieveCaseInsensitiveProperty('applicationId', request)
        if (!applicationId || applicationId.length === 0) {
          exports.error(client, {
            action: action
          }, [Error.throw('invalid_parameter', 'applicationId')])
          return
        }

        let callbackMeta = _.extend(requestMeta, {
          action: 'stream:broadcast',
          originalAction: action,
          applicationId: applicationId,
          id: client.clientId
        })

        exports.send(client, callbackMeta, data)

        let meta = _.extend(requestMeta, {
          action: action,
          applicationId: applicationId
        })

        let clients = exports.socket.clients.filter(function (cl) {
          return cl.subscribedStreams.indexOf(applicationId) !== -1 && cl.clientId !== client.clientId
        })

        exports.broadcast(clients, meta, data)
      }

    } else {
      let error
      if (requestHadActionField) {
        error = Error.invalid_parameter
      } else {
        error = Error.missing_required_field
      }

      let meta = _.extend(requestMeta, {
        action: 'unknown',
        id: client.clientId
      })

      error.detail = 'action'
      exports.error(client, meta, [error])
    }
  } catch (ex) {
    if (!requestMeta) { requestMeta = {} }
    if (request && action) {
      if (typeof action == 'string') {
        let error = Error.server_error
        error.detail = ex.message

        let meta = _.extend(requestMeta, {
          action: action,
          id: client.clientId
        })

        exports.error(client, meta, [error])
        return
      }
    }
    let error = Error.server_error
    error.detail = ex.message

    let meta = _.extend(requestMeta, {
      action: 'unknown',
      id: client.clientId
    })

    exports.error(client, meta, [error])
  }
}

function callAPIMethod (method, data, client, query, action, requestMeta) {
  method.call(null, data, client, query).then(function (response) {
    let data = response.data
    let meta = _.extend(requestMeta, response.meta)
    meta.action = action

    exports.send(client, meta, data)
  }, function (response) {
    let error = response.error
    let meta = _.extend(requestMeta, response.meta)
    meta.action = action

    exports.error(client, meta, [error])
  })
}

exports.send = function (client, meta, data) {
  if (meta.hasOwnProperty('action') === false) {
    winston.error('Missing action parameter in meta response.')
    return
  }

  let response = {
    meta: meta,
    data: data
  }

  client.send(JSON.stringify(response))
}

exports.broadcast = function (clients, meta, data) {
  if (meta.hasOwnProperty('action') === false) {
    winston.error('Missing action parameter in meta response.')
    return
  }

  let response = {
    meta: meta,
    data: data
  }

  let responseString = JSON.stringify(response)
  clients.forEach(function (client) {
    client.send(responseString)
  })
}

exports.error = function (client, meta, errors) {
  if (meta.hasOwnProperty('action') === false) {
    winston.error('Missing action parameter in meta response.')
    return
  }

  if (Object.prototype.toString.call(errors) !== '[object Array]') {
    winston.error('Provided error list was not an array')
    return
  }

  let response = {
    meta: meta,
    errors: errors
  }

  client.send(JSON.stringify(response))
}

exports.authorization = function (query, client, meta) {
  meta.action = 'authorization'

  let accessToken = query.bearer
  if (accessToken) {
    Token.findOne({ where: {
      value: accessToken
    }}).then(function (token) {
      if (!token) {
        exports.error(client, meta, [Permission.authenticationError()])
        return
      }
      User.findById(token.userId).then(function (user) {
        client.user = user.toJSON()

        delete client.user.salt
        delete client.user.password
        delete client.deletedAt

        exports.send(client, { action: 'authorization' }, user)
      }).catch(function (error) {
        exports.error(client, meta, [Error.throw('server_error', error)])
      })
    }).catch(function () {
      exports.error(client, meta, [Permission.authenticationError()])
    })
  } else {
    exports.error(client, meta, [Permission.authenticationError()])
  }
}
<commit_msg>Ensure we do not further execute after calling API method in websocket<commit_after>'use strict'
let winston = require('winston')
let Error = require('./errors')

// Import controllers
let Token = require('./db').Token
let User = require('./db').User
let rat = require('./controllers/rat').Controller
let Permission = require('./permission')
let rescue = require('./controllers/rescue').Controller
let stream = require('./controllers/stream')
let client = require('./controllers/client').Controller
let user = require('./controllers/user').Controller
let _ = require('underscore')

let controllers = {
  rats: {
    create: [rat.create],
    read: [rat.read],
    update: [rat.update, true],
    delete: [rat.delete, true, 'rat.delete']
  },

  rescues: {
    create: [rescue.create, true],
    read: [rescue.read],
    update: [rescue.update, true],
    delete: [rescue.delete, true, 'rescue.delete']
  },

  users: {
    create: [user.create, true],
    read: [user.read, true, 'user.read'],
    update: [user.update, true],
    delete: [user.delete, true, 'user.delete']
  },

  client: {
    create: [client.create, true, 'self.client.create'],
    read: [client.read, true, 'client.read'],
    update: [client.update, true, 'client.update'],
    delete: [client.delete, true, 'client.delete']
  },

  stream: {
    subscribe: [stream.subscribe, true],
    unsubscribe: [stream.unsubscribe, true]
  }
}

exports.retrieveCaseInsensitiveProperty = function (propertyName, obj) {
  if (!obj) { return null }

  propertyName = propertyName.toLowerCase()

  let caseInsensitivePropertyMap = Object.keys(obj).map(function (prop) {
    return prop.toLowerCase()
  })

  let indexOfProperty = caseInsensitivePropertyMap.indexOf(propertyName)

  if (indexOfProperty !== -1) {
    return obj[Object.keys(obj)[indexOfProperty]]
  }
  return null
}

exports.socket = null

exports.received = function (client, requestString) {
  let action, data, requestMeta, request
  try {
    request = JSON.parse(requestString)

    let requestHasValidAction = false
    let requestHadActionField = false
    action = exports.retrieveCaseInsensitiveProperty('action', request)
    data = exports.retrieveCaseInsensitiveProperty('data', request)
    requestMeta = exports.retrieveCaseInsensitiveProperty('meta', request)
    if (!requestMeta) { requestMeta = {} }
    if (!data) { data = {} }
    requestMeta.action = action

    let query = _.clone(request)

    if (action) {
      requestHadActionField = true
      if (typeof action == 'string') {
        if (action === 'authorization') {
          exports.authorization(query, client, requestMeta)
          return
        }

        requestHasValidAction = action.length > 2 && action.includes(':')
      }
    }

    if (requestHasValidAction === true) {
      let requestSections = action.split(':')
      let namespace = requestSections[0].toLowerCase()

      if (controllers.hasOwnProperty(namespace)) {
        let controller = controllers[namespace]
        let method = requestSections[1].toLowerCase()

        if (method && controller[method]) {
          if (controller[method].length > 1) {
            let isAuthenticated = controller[method][1] === true

            if (isAuthenticated) {
              if (client.user) {
                if (controller[method].length > 2) {
                  Permission.require(controller[method][2]).then(function () {
                    client.websocket = exports
                    callAPIMethod(controller[method][0], data, client, query, action, requestMeta)
                    return
                  }).catch(function (error) {
                    exports.error(client, requestMeta, [error])
                    return
                  })
                }
                callAPIMethod(controller[method][0], data, client, query, action, requestMeta)
              } else {
                return
                exports.error(client, requestMeta, [Permission.authenticationError()])
                return
              }
            }

            callAPIMethod(controller[method][0], data, client, query, action, requestMeta)
            return
          }
        }
        exports.error(client, {
          action: action
        }, [Error.throw('invalid_parameter', 'action')])
      } else {
        let applicationId = exports.retrieveCaseInsensitiveProperty('applicationId', request)
        if (!applicationId || applicationId.length === 0) {
          exports.error(client, {
            action: action
          }, [Error.throw('invalid_parameter', 'applicationId')])
          return
        }

        let callbackMeta = _.extend(requestMeta, {
          action: 'stream:broadcast',
          originalAction: action,
          applicationId: applicationId,
          id: client.clientId
        })

        exports.send(client, callbackMeta, data)

        let meta = _.extend(requestMeta, {
          action: action,
          applicationId: applicationId
        })

        let clients = exports.socket.clients.filter(function (cl) {
          return cl.subscribedStreams.indexOf(applicationId) !== -1 && cl.clientId !== client.clientId
        })

        exports.broadcast(clients, meta, data)
      }

    } else {
      let error
      if (requestHadActionField) {
        error = Error.invalid_parameter
      } else {
        error = Error.missing_required_field
      }

      let meta = _.extend(requestMeta, {
        action: 'unknown',
        id: client.clientId
      })

      error.detail = 'action'
      exports.error(client, meta, [error])
    }
  } catch (ex) {
    if (!requestMeta) { requestMeta = {} }
    if (request && action) {
      if (typeof action == 'string') {
        let error = Error.server_error
        error.detail = ex.message

        let meta = _.extend(requestMeta, {
          action: action,
          id: client.clientId
        })

        exports.error(client, meta, [error])
        return
      }
    }
    let error = Error.server_error
    error.detail = ex.message

    let meta = _.extend(requestMeta, {
      action: 'unknown',
      id: client.clientId
    })

    exports.error(client, meta, [error])
  }
}

function callAPIMethod (method, data, client, query, action, requestMeta) {
  method.call(null, data, client, query).then(function (response) {
    let data = response.data
    let meta = _.extend(requestMeta, response.meta)
    meta.action = action

    exports.send(client, meta, data)
  }, function (response) {
    let error = response.error
    let meta = _.extend(requestMeta, response.meta)
    meta.action = action

    exports.error(client, meta, [error])
  })
}

exports.send = function (client, meta, data) {
  if (meta.hasOwnProperty('action') === false) {
    winston.error('Missing action parameter in meta response.')
    return
  }

  let response = {
    meta: meta,
    data: data
  }

  client.send(JSON.stringify(response))
}

exports.broadcast = function (clients, meta, data) {
  if (meta.hasOwnProperty('action') === false) {
    winston.error('Missing action parameter in meta response.')
    return
  }

  let response = {
    meta: meta,
    data: data
  }

  let responseString = JSON.stringify(response)
  clients.forEach(function (client) {
    client.send(responseString)
  })
}

exports.error = function (client, meta, errors) {
  if (meta.hasOwnProperty('action') === false) {
    winston.error('Missing action parameter in meta response.')
    return
  }

  if (Object.prototype.toString.call(errors) !== '[object Array]') {
    winston.error('Provided error list was not an array')
    return
  }

  let response = {
    meta: meta,
    errors: errors
  }

  client.send(JSON.stringify(response))
}

exports.authorization = function (query, client, meta) {
  meta.action = 'authorization'

  let accessToken = query.bearer
  if (accessToken) {
    Token.findOne({ where: {
      value: accessToken
    }}).then(function (token) {
      if (!token) {
        exports.error(client, meta, [Permission.authenticationError()])
        return
      }
      User.findById(token.userId).then(function (user) {
        client.user = user.toJSON()

        delete client.user.salt
        delete client.user.password
        delete client.deletedAt

        exports.send(client, { action: 'authorization' }, user)
      }).catch(function (error) {
        exports.error(client, meta, [Error.throw('server_error', error)])
      })
    }).catch(function () {
      exports.error(client, meta, [Permission.authenticationError()])
    })
  } else {
    exports.error(client, meta, [Permission.authenticationError()])
  }
}
<|endoftext|>


<commit_before>import { run } from '@ember/runloop';
import { capitalize } from '@ember/string';
import { getOwner } from '@ember/application';
import { tryInvoke, isEqual } from '@ember/utils';
import EmberObject, { set, get } from '@ember/object';
import ConnectionMonitor from 'ember-cable/core/connection_monitor';

export default EmberObject.extend({
  consumer: null,
  connected: false,
  _reopenTimer: null,

  init() {
    this._super(...arguments);
    this.open();
    this.monitor = ConnectionMonitor.create(getOwner(this).ownerInjection(), {
      connection: this
    });
  },

  send(data) {
    if (this.isOpen()) {
      get(this, 'webSocket').send(JSON.stringify(data));
    }
  },

  open() {
    let ws = new WebSocket(get(this, 'consumer.url'));

    ['open', 'close', 'error', 'message'].forEach(eventName => {
      ws[`on${eventName}`] = event => {
        run(() => tryInvoke(this, `on${capitalize(eventName)}`, [event]));
      };
    });

    set(this, 'webSocket', ws);
  },

  close() {
    tryInvoke(get(this, 'webSocket'), 'close');
  },

  reopen() {
    if (this.isClose()) {
      this.open();
    } else {
      this.close();
      this._reopenTimer = setTimeout(run.bind(this, 'reopen'), 500);
    }
  },

  willDestroy() {
    this._super();
    clearTimeout(this._reopenTimer);
    this.monitor.destroy();
  },

  isClose() {
    return !this.isOpen();
  },

  isOpen() {
    return (
      isEqual(get(this, 'connected'), true) &&
      isEqual(get(this, 'webSocket').readyState, get(this, 'webSocket').OPEN)
    );
  },

  isConnecting() {
    return isEqual(
      get(this, 'webSocket').readyState,
      get(this, 'webSocket').CONNECTING
    );
  },

  disconnect() {
    set(this, 'connected', false);
    get(this, 'consumer.subscriptions').notifyAll('disconnected');
  },

  onMessage(event) {
    let data = JSON.parse(event.data);
    switch (data.type) {
      case 'welcome':
        get(this, 'monitor').connected();
        break;
      case 'ping':
        get(this, 'monitor').ping();
        break;
      case 'confirm_subscription':
        get(this, 'consumer.subscriptions').notify(
          data.identifier,
          'connected'
        );
        break;
      case 'reject_subscription':
        get(this, 'consumer.subscriptions').reject(data.identifier);
        break;
      default:
        get(this, 'consumer.subscriptions').notify(
          data.identifier,
          'received',
          data.message
        );
    }
  },

  onOpen() {
    set(this, 'connected', true);
    get(this, 'consumer.subscriptions').reload();
  },

  onClose() {
    this.disconnect();
  },

  onError() {
    this.disconnect();
  }
});
<commit_msg>Remove events from webSocket when closing<commit_after>import { run } from '@ember/runloop';
import { capitalize } from '@ember/string';
import { getOwner } from '@ember/application';
import { tryInvoke, isEqual } from '@ember/utils';
import EmberObject, { set, get } from '@ember/object';
import ConnectionMonitor from 'ember-cable/core/connection_monitor';

export default EmberObject.extend({
  consumer: null,
  connected: false,
  _reopenTimer: null,

  init() {
    this._super(...arguments);
    this.open();
    this.monitor = ConnectionMonitor.create(getOwner(this).ownerInjection(), {
      connection: this
    });
  },

  send(data) {
    if (this.isOpen()) {
      get(this, 'webSocket').send(JSON.stringify(data));
    }
  },

  open() {
    let ws = new WebSocket(get(this, 'consumer.url'));

    ['open', 'close', 'error', 'message'].forEach(eventName => {
      ws[`on${eventName}`] = event => {
        run(() => tryInvoke(this, `on${capitalize(eventName)}`, [event]));
      };
    });

    set(this, 'webSocket', ws);
  },

  close() {
    let ws = this.get('webSocket');

    ['open', 'close', 'error', 'message'].forEach(eventName => {
      ws[`on${eventName}`] = null;
    });

    tryInvoke(get(this, 'webSocket'), 'close');
    this.disconnect();
  },

  reopen() {
    if (this.isClose()) {
      this.open();
    } else {
      this.close();
      this._reopenTimer = setTimeout(run.bind(this, 'reopen'), 500);
    }
  },

  willDestroy() {
    this._super();
    clearTimeout(this._reopenTimer);
    this.monitor.destroy();
  },

  isClose() {
    return !this.isOpen();
  },

  isOpen() {
    return (
      isEqual(get(this, 'connected'), true) &&
      isEqual(get(this, 'webSocket').readyState, get(this, 'webSocket').OPEN)
    );
  },

  isConnecting() {
    return isEqual(
      get(this, 'webSocket').readyState,
      get(this, 'webSocket').CONNECTING
    );
  },

  disconnect() {
    set(this, 'connected', false);
    get(this, 'consumer.subscriptions').notifyAll('disconnected');
  },

  onMessage(event) {
    let data = JSON.parse(event.data);
    switch (data.type) {
      case 'welcome':
        get(this, 'monitor').connected();
        break;
      case 'ping':
        get(this, 'monitor').ping();
        break;
      case 'confirm_subscription':
        get(this, 'consumer.subscriptions').notify(
          data.identifier,
          'connected'
        );
        break;
      case 'reject_subscription':
        get(this, 'consumer.subscriptions').reject(data.identifier);
        break;
      default:
        get(this, 'consumer.subscriptions').notify(
          data.identifier,
          'received',
          data.message
        );
    }
  },

  onOpen() {
    set(this, 'connected', true);
    get(this, 'consumer.subscriptions').reload();
  },

  onClose() {
    this.disconnect();
  },

  onError() {
    this.disconnect();
  }
});
<|endoftext|>


<commit_before>import { Model } from 'ember-cli-mirage';
import { toCollectionName } from 'ember-cli-mirage/utils/normalize-name';
import assert from 'ember-cli-mirage/assert';

export default Model.extend({
  // the associations in this list will be destroyed in beforeDestroy
  // TODO: define cleaner hasOne / belongsToMany relationships
  childrenAssociations: [],
  // the associations for which you can add / remove on the GUI
  allowChangeNbAssociations: [],
  // the flags to display on the GUI
  flags: [],
  default() {
    // override in model to setup when instantiated
  },

  updateAttrs(hash) {
    this.update(hash);
    // https://github.com/samselikoff/ember-cli-mirage/issues/719
    return this;
  },

  typeOf(relName) {
    let relNames = relName.pluralize();
    return this[relNames] ? 'hasMany' : 'belongsTo';
  },

  _beforeDestroy() {
    this.associationKeys.forEach((relName) => {
      if (this.childrenAssociations.indexOf(relName) > -1) {
        this.hasNo(relName);
      }
    });
  },

  destroy() {
    this._beforeDestroy();
    let collection = toCollectionName(this.modelName);
    this._schema.db[collection].remove(this.attrs.id);
  },

  // added call to 'default'
  save() {
    let collection = toCollectionName(this.modelName);
    if (this.isNew()) {
      // Update the attrs with the db response
      this.attrs = this._schema.db[collection].insert(this.attrs);

      // Ensure the id getter/setter is set
      this._definePlainAttribute('id');
      this.default();

    } else {
      this._schema.db[collection].update(this.attrs.id, this.attrs);
    }

    // Update associated children
    this._saveAssociations();

    return this;
  },

  hasNo(relName) {
    relName = relName.singularize();
    return this.typeOf(relName) === 'hasMany' ?
    this.hasNoOfMany(relName) :
    this.hasNoOfOne(relName);
  },
  hasNoOfOne(relName) {
    relName = relName.singularize();
    let rel = this[relName];
    if (rel) {
      let assoc = this.belongsToAssociations[relName];
      let {modelName} = assoc;
      let inverseRelName = assoc.opts.inverse || this.modelName;
      //rel[inverseRelName] = null;
      //rel.destroy();
      rel._beforeDestroy();
    }
    this[relName] = null;
    this.save();
    return this;
  },
  hasNoOfMany(relName) {
    relName = relName.singularize();
    // TODO: create a general deleteXX method for hasMany
    let rels = this[`${relName.pluralize()}`].models;
    this[`${relName.pluralize()}`] = [];
    let assoc = this.hasManyAssociations[relName.pluralize()];
    let {modelName} = assoc;
    let inverseRelName = assoc.opts.inverse || this.modelName;
    rels.forEach((rel) => {
      //debugger;
      rel._beforeDestroy();
      //rel.update(`${inverseRelName}Id`, null);
      //rel.destroy();
    });
    this[`${relName.pluralize()}`] = [];
    this.save();
    return this;
  },

  hasMulti(relName, nb, attrs) {
    relName = relName.singularize();
    nb = nb || 2;
    let rels = this[`${relName.pluralize()}`].models,
      initialNumber = rels.length;
    // let rel = this[`create${relName.capitalize()}`]();
    let assoc = this.hasManyAssociations[relName.pluralize()];
    let {modelName} = assoc;
    let inverseRelName = assoc.opts.inverse || this.modelName;
    if (attrs) {
      for (let i = 0; i < initialNumber; i++) {
        rels[i].update(attrs);
      }
    }
    for (let i = initialNumber; i < nb; i++) {
      let hash = attrs || {};
      hash[`${inverseRelName.camelize()}Id`] = this.id;
      rels.push(server.create(modelName, hash));
    }
    for (let i = nb; i < initialNumber; i++) {
      let rel = rels[i];
      rels.removeObject(rel);
      // rel.destroy();
    }
    this[`${relName.pluralize()}`] = rels;
    this.save();
    return this[`${relName.pluralize()}`].models;
  },

  hasOne(relName, attrs) { // exactly one
    relName = relName.singularize();
    let model = this.typeOf(relName) === 'hasMany' ?
    this.hasOneOfMany(relName) :
    this.hasOneOfOne(relName);
    if (attrs) {
      return model.updateAttrs(attrs);
    } else {
      return model;
    }
  },
  hasOneOfMany(relName) {
    relName = relName.singularize();
    assert(
      this.hasManyAssociations[relName.pluralize()],
      `can not find hasManyAssociations with name ${relName.pluralize()} in ${this.modelName} model`
    );
    // TODO: create a general deleteXX method for hasMany
    let rels = this[`${relName.pluralize()}`].models;
    let rel;
    let {length} = rels;
    if (length) {
      rel = rels[0];
      if (length > 1) {
        this[`${relName.pluralize()}`] = [rel];
        this.save();
      }
      /*for (let i = 1; i < length; i++) {
        rels[i].destroy();
      }*/
    } else {
      let assoc = this.hasManyAssociations[relName.pluralize()];
      let {modelName} = assoc;
      let inverseRelName = assoc.opts.inverse || this.modelName;
      // let inverseRelNameKey = assoc.getForeignKey();
      let hash = {};
      hash[`${inverseRelName.camelize()}Id`] = this.id;
      rel = server.create(modelName, hash);
      this[`${relName.pluralize()}`] = [rel];
      this.save();
    }
    return rel;
  },
  hasOneOfOne(relName) {
    relName = relName.singularize();
    assert(
      this.belongsToAssociations[relName],
      `can not find belongsToAssociation with name ${relName} in ${this.modelName} model`
    );
    let rel = this[relName];
    if (!rel) {
      let assoc = this.belongsToAssociations[relName];
      let {modelName} = assoc;
      let inverseRelName = assoc.opts.inverse || this.modelName;
      let hash = {};
      hash[`${inverseRelName.camelize()}Id`] = this.id;
      rel = server.create(modelName, hash);
      this.update(`${relName}Id`, rel.id);
    }
    return rel;
  },

  addRelationship(relName, attrs) {
    relName = relName.singularize();
    let model = this.typeOf(relName) === 'hasMany' ?
    this.addRelationshipOfMany(relName) :
    this.addRelationshipOfOne(relName);
    if (attrs) {
      return model.updateAttrs(attrs);
    } else {
      return model;
    }
  },
  deleteRelationship(relName) {
    relName = relName.singularize();
    return this.typeOf(relName) === 'hasMany' ?
    this.deleteRelationshipOfMany(relName) :
    this.deleteRelationshipOfOne(relName);
  },
  addRelationshipOfOne(relName) {
    relName = relName.singularize();
    assert(
      this.belongsToAssociations[relName],
      `can not find belongsToAssociation with name ${relName} in ${this.modelName} model`
    );
    let rel = this[relName];
    assert(
      !rel,
      `can not add a model to an already existing belongsTo relationship ${relName} in ${this.modelName} model`
    );
    return this.hasOneOfOne(relName);
  },
  deleteRelationshipOfOne(relName) {
    relName = relName.singularize();
    assert(
      this.belongsToAssociations[relName],
      `can not find belongsToAssociation with name ${relName} in ${this.modelName} model`
    );
    let rel = this[relName];
    assert(
      rel,
      `can not remove a model to an already empty belongsTo relationship ${relName} in ${this.modelName} model`
    );
    return this.hasNoOfOne(relName);
  },
  addRelationshipOfMany(relName) {
    relName = relName.singularize();
    let rels = this[`${relName.pluralize()}`].models;
    let assoc = this.hasManyAssociations[relName.pluralize()];
    let {modelName} = assoc;
    let inverseRelName = assoc.opts.inverse || this.modelName;
    let hash = {};
    hash[`${inverseRelName.camelize()}Id`] = this.id;
    let model = server.create(modelName, hash);
    rels.push(model);
    this[`${relName.pluralize()}`] = rels;
    this.save();
    return model;
  },
  deleteRelationshipOfMany(relName, model) {
    relName = relName.singularize();
    let rels = this[`${relName.pluralize()}`].models;
    model = model || rels[rels.length-1];
    rels.removeObject(model);
    //model.destroy();
    this[`${relName.pluralize()}`] = rels;
    this.save();
    return this;
  },
  snapshot() {
    let hash = {};
    hash.flags = this.flags.map((flag) => {
      return {
        name: flag.name,
        value: this[flag.name]
      };
    });
    hash.associations = this.childrenAssociations.map((relName) => {
      let rel = this[relName],
        belongsTo = !rel || !rel.models,
        hasMany = !belongsTo;
      return {
        name: relName,
        count: belongsTo ? (rel ? 1 : 0) : rel.models.length,//eslint-disable-line
        models: belongsTo ? (rel ? [rel.snapshot()] : []) : rel.models.map((model) => {return model.snapshot();})
      };
    });
    return hash;
  },
  fromSnapshot(snapshot) {
    if (snapshot) {
      snapshot.flags.forEach(({name, value}) => {
        let flag = this.flags.findBy('name', name);
        if (flag.method) {
          this[flag.method](value);
        } else {
          this.update(name, value);
        }
      }),
      snapshot.associations.forEach(({name, count, models}) => {
        let rels = [];
        switch (count) {
          case 0:
            this.hasNo(name);
            break;
          case 1:
            rels = [this.hasOne(name)];
            break;
          default:
            rels = this.hasMulti(name, count);
        }
        rels.forEach((rel, index) => {
          rel.fromSnapshot(models[index]);
        });
      });
    }
  },
  //https://github.com/samselikoff/ember-cli-mirage/issues/1061
  update(key, val) {
    let attrs;
    if (key == null) {
      return this;
    }

    if (typeof key === 'object') {
      attrs = key;
    } else {
      (attrs = {})[key] = val;
    }

    Object.keys(attrs).forEach(function(attr) {
      if (!Object.keys(this.belongsToAssociations).includes(attr) &&
        !Object.keys(this.hasManyAssociations).includes(attr)) {
        this._definePlainAttribute(attr);
      }
      this[attr] = attrs[attr];
    }, this);

    this.save();

    return this;
  }
});
<commit_msg>inflectors<commit_after>import { Model } from 'ember-cli-mirage';
import { toCollectionName } from 'ember-cli-mirage/utils/normalize-name';
import assert from 'ember-cli-mirage/assert';
import { pluralize, camelize, singularize } from 'ember-inflector';

export default Model.extend({
  // the associations in this list will be destroyed in beforeDestroy
  // TODO: define cleaner hasOne / belongsToMany relationships
  childrenAssociations: [],
  // the associations for which you can add / remove on the GUI
  allowChangeNbAssociations: [],
  // the flags to display on the GUI
  flags: [],
  default() {
    // override in model to setup when instantiated
  },

  updateAttrs(hash) {
    this.update(hash);
    // https://github.com/samselikoff/ember-cli-mirage/issues/719
    return this;
  },

  typeOf(relName) {
    let relNames = pluralize(relName);
    return this[relNames] ? 'hasMany' : 'belongsTo';
  },

  _beforeDestroy() {
    this.associationKeys.forEach((relName) => {
      if (this.childrenAssociations.indexOf(relName) > -1) {
        this.hasNo(relName);
      }
    });
  },

  destroy() {
    this._beforeDestroy();
    let collection = toCollectionName(this.modelName);
    this._schema.db[collection].remove(this.attrs.id);
  },

  // added call to 'default'
  save() {
    let collection = toCollectionName(this.modelName);
    if (this.isNew()) {
      // Update the attrs with the db response
      this.attrs = this._schema.db[collection].insert(this.attrs);

      // Ensure the id getter/setter is set
      this._definePlainAttribute('id');
      this.default();

    } else {
      this._schema.db[collection].update(this.attrs.id, this.attrs);
    }

    // Update associated children
    this._saveAssociations();

    return this;
  },

  hasNo(relName) {
    relName = singularize(relName);
    return this.typeOf(relName) === 'hasMany' ?
    this.hasNoOfMany(relName) :
    this.hasNoOfOne(relName);
  },
  hasNoOfOne(relName) {
    relName = singularize(relName);
    let rel = this[relName];
    if (rel) {
      let assoc = this.belongsToAssociations[relName];
      let {modelName} = assoc;
      let inverseRelName = assoc.opts.inverse || this.modelName;
      //rel[inverseRelName] = null;
      //rel.destroy();
      rel._beforeDestroy();
    }
    this[relName] = null;
    this.save();
    return this;
  },
  hasNoOfMany(relName) {
    relName = singularize(relName);
    // TODO: create a general deleteXX method for hasMany
    let relNames = pluralize(relName),
      rels = this[relNames].models;
    this[relNames] = [];
    let assoc = this.hasManyAssociations[relNames];
    let {modelName} = assoc;
    let inverseRelName = assoc.opts.inverse || this.modelName;
    rels.forEach((rel) => {
      //debugger;
      rel._beforeDestroy();
      //rel.update(`${inverseRelName}Id`, null);
      //rel.destroy();
    });
    this[relNames] = [];
    this.save();
    return this;
  },

  hasMulti(relName, nb, attrs) {
    relName = singularize(relName);
    let relNames = pluralize(relName);
    nb = nb || 2;
    let rels = this[relNames].models,
      initialNumber = rels.length;
    // let rel = this[`create${relName.capitalize()}`]();
    let assoc = this.hasManyAssociations[relNames];
    let {modelName} = assoc;
    let inverseRelName = assoc.opts.inverse || this.modelName;
    if (attrs) {
      for (let i = 0; i < initialNumber; i++) {
        rels[i].update(attrs);
      }
    }
    for (let i = initialNumber; i < nb; i++) {
      let hash = attrs || {};
      hash[`${camelize(inverseRelName)}Id`] = this.id;
      rels.push(server.create(modelName, hash));
    }
    for (let i = nb; i < initialNumber; i++) {
      let rel = rels[i];
      rels.removeObject(rel);
      // rel.destroy();
    }
    this[relNames] = rels;
    this.save();
    return this[relNames].models;
  },

  hasOne(relName, attrs) { // exactly one
    relName = singularize(relName);
    let model = this.typeOf(relName) === 'hasMany' ?
    this.hasOneOfMany(relName) :
    this.hasOneOfOne(relName);
    if (attrs) {
      return model.updateAttrs(attrs);
    } else {
      return model;
    }
  },
  hasOneOfMany(relName) {
    relName = singularize(relName);
    let relNames = pluralize(relName);
    assert(
      this.hasManyAssociations[relNames],
      `can not find hasManyAssociations with name ${relNames} in ${this.modelName} model`
    );
    // TODO: create a general deleteXX method for hasMany
    let rels = this[relNames].models;
    let rel;
    let {length} = rels;
    if (length) {
      rel = rels[0];
      if (length > 1) {
        this[relNames] = [rel];
        this.save();
      }
      /*for (let i = 1; i < length; i++) {
        rels[i].destroy();
      }*/
    } else {
      let assoc = this.hasManyAssociations[relNames];
      let {modelName} = assoc;
      let inverseRelName = assoc.opts.inverse || this.modelName;
      // let inverseRelNameKey = assoc.getForeignKey();
      let hash = {};
      hash[`${camelize(inverseRelName)}Id`] = this.id;
      rel = server.create(modelName, hash);
      this[relNames] = [rel];
      this.save();
    }
    return rel;
  },
  hasOneOfOne(relName) {
    relName = singularize(relName);
    assert(
      this.belongsToAssociations[relName],
      `can not find belongsToAssociation with name ${relName} in ${this.modelName} model`
    );
    let rel = this[relName];
    if (!rel) {
      let assoc = this.belongsToAssociations[relName];
      let {modelName} = assoc;
      let inverseRelName = assoc.opts.inverse || this.modelName;
      let hash = {};
      hash[`${camelize(inverseRelName)}Id`] = this.id;
      rel = server.create(modelName, hash);
      this.update(`${relName}Id`, rel.id);
    }
    return rel;
  },

  addRelationship(relName, attrs) {
    relName = singularize(relName);
    let model = this.typeOf(relName) === 'hasMany' ?
    this.addRelationshipOfMany(relName) :
    this.addRelationshipOfOne(relName);
    if (attrs) {
      return model.updateAttrs(attrs);
    } else {
      return model;
    }
  },
  deleteRelationship(relName) {
    relName = singularize(relName);
    return this.typeOf(relName) === 'hasMany' ?
    this.deleteRelationshipOfMany(relName) :
    this.deleteRelationshipOfOne(relName);
  },
  addRelationshipOfOne(relName) {
    relName = singularize(relName);
    assert(
      this.belongsToAssociations[relName],
      `can not find belongsToAssociation with name ${relName} in ${this.modelName} model`
    );
    let rel = this[relName];
    assert(
      !rel,
      `can not add a model to an already existing belongsTo relationship ${relName} in ${this.modelName} model`
    );
    return this.hasOneOfOne(relName);
  },
  deleteRelationshipOfOne(relName) {
    relName = singularize(relName);
    assert(
      this.belongsToAssociations[relName],
      `can not find belongsToAssociation with name ${relName} in ${this.modelName} model`
    );
    let rel = this[relName];
    assert(
      rel,
      `can not remove a model to an already empty belongsTo relationship ${relName} in ${this.modelName} model`
    );
    return this.hasNoOfOne(relName);
  },
  addRelationshipOfMany(relName) {
    relName = singularize(relName);
    let relNames = pluralize(relName);
    let rels = this[relNames].models;
    let assoc = this.hasManyAssociations[relNames];
    let {modelName} = assoc;
    let inverseRelName = assoc.opts.inverse || this.modelName;
    let hash = {};
    hash[`${camelize(inverseRelName)}Id`] = this.id;
    let model = server.create(modelName, hash);
    rels.push(model);
    this[relNames] = rels;
    this.save();
    return model;
  },
  deleteRelationshipOfMany(relName, model) {
    relName = singularize(relName);
    let relNames = pluralize(relName);
    let rels = this[relNames].models;
    model = model || rels[rels.length-1];
    rels.removeObject(model);
    //model.destroy();
    this[relNames] = rels;
    this.save();
    return this;
  },
  snapshot() {
    let hash = {};
    hash.flags = this.flags.map((flag) => {
      return {
        name: flag.name,
        value: this[flag.name]
      };
    });
    hash.associations = this.childrenAssociations.map((relName) => {
      let rel = this[relName],
        belongsTo = !rel || !rel.models,
        hasMany = !belongsTo;
      return {
        name: relName,
        count: belongsTo ? (rel ? 1 : 0) : rel.models.length,//eslint-disable-line
        models: belongsTo ? (rel ? [rel.snapshot()] : []) : rel.models.map((model) => {return model.snapshot();})
      };
    });
    return hash;
  },
  fromSnapshot(snapshot) {
    if (snapshot) {
      snapshot.flags.forEach(({name, value}) => {
        let flag = this.flags.findBy('name', name);
        if (flag.method) {
          this[flag.method](value);
        } else {
          this.update(name, value);
        }
      }),
      snapshot.associations.forEach(({name, count, models}) => {
        let rels = [];
        switch (count) {
          case 0:
            this.hasNo(name);
            break;
          case 1:
            rels = [this.hasOne(name)];
            break;
          default:
            rels = this.hasMulti(name, count);
        }
        rels.forEach((rel, index) => {
          rel.fromSnapshot(models[index]);
        });
      });
    }
  },
  //https://github.com/samselikoff/ember-cli-mirage/issues/1061
  update(key, val) {
    let attrs;
    if (key == null) {
      return this;
    }

    if (typeof key === 'object') {
      attrs = key;
    } else {
      (attrs = {})[key] = val;
    }

    Object.keys(attrs).forEach(function(attr) {
      if (!Object.keys(this.belongsToAssociations).includes(attr) &&
        !Object.keys(this.hasManyAssociations).includes(attr)) {
        this._definePlainAttribute(attr);
      }
      this[attr] = attrs[attr];
    }, this);

    this.save();

    return this;
  }
});
<|endoftext|>


<commit_before>export default function isObject(val) {
  return (
    val !== null &&
    typeof val === 'object' &&
    !(val instanceof Date || val instanceof RegExp) &&
    !Array.isArray(val) &&
    !val.toArray
  );
}
<commit_msg>[Bug]: use isArray for better Ember Array detection (#557)<commit_after>import { isArray } from '@ember/array';

/**
 * Employ Ember strategies for isObject detection
 * @method isObject
 */
export default function isObject(val) {
  return val !== null && typeof val === 'object' && !(val instanceof Date || val instanceof RegExp) && !isArray(val);
}
<|endoftext|>


<commit_before>class MapLabel extends google.maps.OverlayView {
  constructor(latlng, options) {
    super(...arguments);

    options = options || {};

    this.latlng = latlng;

    this._opts = options;
    this._element = document.createElement('div');
    this._element.className = 'google-maps-markup-map-label';
    this._element.style.position = 'absolute';

    if (options.className) {
      this._element.className += ' ' + options.className;
    }

    if (options.defaultLabel) {
      this._element.textContent = options.defaultLabel;
    }
  }

  // Required by GMaps
  onAdd() {
    var pane = this.getPanes().markerLayer;

    if (pane) {
      pane.appendChild(this._element);
    }
  }

  // Required by GMaps
  draw() {
    var map = this.getMap();
    var projection = this.getProjection();
    var position = projection.fromLatLngToDivPixel(this.latlng);
    var div = this._element;

    if (position && position.x && position.y) {
      let width = this._element.clientWidth;
      let height = this._element.clientHeight;

      div.style.display = 'block';

      if (map) {
        let zoom = map.getZoom();

        this.updateScale(zoom, this.lastZoom);
        this.lastZoom = zoom;
      }

      div.style.left = position.x - (width / 2) + 'px';
      div.style.top = position.y - (height / 2) + 'px';
    } else {
      div.style.display = 'none';
    }
  }

  // Required by GMaps
  onRemove() {
    this._element.parentNode.removeChild(this._element);
  }

  set label(value) {
    this._element.textContent = value;
  }

  get label() {
    return this._element.textContent;
  }

  set position(value) {
    this.latlng = value;
    this.draw();
  }

  get position() {
    return this.latlng;
  }

  hide() {
    this._element.style.display = 'none';
  }

  show() {
    this._element.style.display = 'block';
  }

  updateScale(newZoom, oldZoom) {
    if (oldZoom === undefined) {
      this._element.style.transform = 'scale(1)';
      this.scale = 1;
      this.scaleMaxZoom = newZoom;
      return;
    }

    if (newZoom < oldZoom) {
      this.scale -= 0.2;
    } else if (newZoom > oldZoom) {
      this.scale += 0.2;
    }

    if (newZoom >= this.scaleMaxZoom) {
      this.scale = 1;
    }

    if (this.scale < 0) {
      this.scale = 0;
    }

    this._element.style.transform = `scale(${this.scale})`;
  }
}

export default MapLabel;
<commit_msg>Fix when scale goes past 0<commit_after>class MapLabel extends google.maps.OverlayView {
  constructor(latlng, options) {
    super(...arguments);

    options = options || {};

    this.latlng = latlng;

    this._opts = options;
    this._element = document.createElement('div');
    this._element.className = 'google-maps-markup-map-label';
    this._element.style.position = 'absolute';

    if (options.className) {
      this._element.className += ' ' + options.className;
    }

    if (options.defaultLabel) {
      this._element.textContent = options.defaultLabel;
    }
  }

  // Required by GMaps
  onAdd() {
    var pane = this.getPanes().markerLayer;

    if (pane) {
      pane.appendChild(this._element);
    }
  }

  // Required by GMaps
  draw() {
    var map = this.getMap();
    var projection = this.getProjection();
    var position = projection.fromLatLngToDivPixel(this.latlng);
    var div = this._element;

    if (position && position.x && position.y) {
      let width = this._element.clientWidth;
      let height = this._element.clientHeight;

      div.style.display = 'block';

      if (map) {
        let zoom = map.getZoom();

        this.updateScale(zoom, this.lastZoom);
        this.lastZoom = zoom;
      }

      div.style.left = position.x - (width / 2) + 'px';
      div.style.top = position.y - (height / 2) + 'px';
    } else {
      div.style.display = 'none';
    }
  }

  // Required by GMaps
  onRemove() {
    this._element.parentNode.removeChild(this._element);
  }

  set label(value) {
    this._element.textContent = value;
  }

  get label() {
    return this._element.textContent;
  }

  set position(value) {
    this.latlng = value;
    this.draw();
  }

  get position() {
    return this.latlng;
  }

  hide() {
    this._element.style.display = 'none';
  }

  show() {
    this._element.style.display = 'block';
  }

  updateScale(newZoom, oldZoom) {
    if (oldZoom === undefined) {
      this._element.style.transform = 'scale(1)';
      this.scale = 1;
      this.scaleMaxZoom = newZoom;
      return;
    }

    if (newZoom < oldZoom) {
      this.scale -= 0.2;
    } else if (newZoom > oldZoom) {
      this.scale += 0.2;
    }

    if (newZoom >= this.scaleMaxZoom) {
      this.scale = 1;
    }

    if (this.scale >= 0) {
      this._element.style.transform = `scale(${this.scale})`;
    }
  }
}

export default MapLabel;
<|endoftext|>


<commit_before>import React from "react";
import { Link } from "gatsby";
import { prepareTitle } from "../common.js";
import Layout from "../components/layout";
import styled from "@emotion/styled";
import SEO from "../components/seo";

const VolumeWrapper = styled.div`
  border-top: 1px solid #faf9f3;
`;

const Volume = styled.div`
  max-width: 960px;
  margin: 0 auto;
  font-size: 0.92rem;
`;

const VolumeTitle = styled.h2`
  padding-left: 24px;
  padding-right: 24px;
`;

const DiscourseList = styled.div`
  -moz-border-radius: 2px;
  -webkit-border-radius: 2px;
  border-bottom: 1px solid #e3e1ce;
  border-left: 1px solid #e3e1ce;
  border-spacing: 0;
  border-top: 1px solid #e3e1ce;
  clear: both;
  max-width: 100%;
  background-color: white;
  margin-bottom: 16px;
  @media (max-width: 640px) {
    padding: 12px;
  }
`;

const DiscourseListItem = styled.div`
  display: flex;
  @media (max-width: 640px) {
    flex-direction: column;
    margin-top: 24px;
    margin-bottom: 24px;
  }
  @media (min-width: 641px) {
    margin-top: 8px;
    margin-bottom: 8px;
  }
  &:first-of-type {
    margin-top: 8px;
  }
  &:last-of-type {
    margin-bottom: 8px;
  }
  &:hover {
    background-color: #f8f8f8;
  }
`;

const Pages = styled.div`
  width: 80px;
  padding: 4px 8px;
  @media (max-width: 640px) {
    order: 4;
    padding-left: 24px;
  }
  @media (min-width: 641px) {
    padding-left: 12px;
  }
`;

const DateComponent = styled.div`
  width: 145px;
  padding: 4px 8px;
  @media (max-width: 640px) {
    padding-left: 24px;
    order: 3;
  }
`;

const Speaker = styled.div`
  padding: 4px 8px;
  @media (max-width: 640px) {
    order: 2;
    padding-left: 24px;
  }
  @media (min-width: 641px) {
    width: 130px;
    padding-left: 24px;
  }
`;
const Title = styled.div`
  flex: 1;
  @media (max-width: 640px) {
    order: 1;
  }
  padding: 4px 8px;
  padding-right: 12px;
`;

export default ({
  pageContext: { volumeNumbers, volumeNumber, discourses }
}) => {
  return (
    <Layout volumeNumbers={volumeNumbers} volumeNumber={volumeNumber}>
      <SEO title={`Journal of Discourses, vol. ${volumeNumber}`} />
      <VolumeWrapper>
        <Volume>
          <VolumeTitle>Volume {volumeNumber}</VolumeTitle>
          <DiscourseList>
            {discourses.map(discourse => {
              const event = new Date();
              if (discourse.date) {
                event.setFullYear(parseInt(discourse.date.substr(0, 4), 10));
                event.setMonth(parseInt(discourse.date.substr(5, 7), 10) - 1);
                event.setDate(parseInt(discourse.date.substr(8, 10), 10));
                event.setUTCHours(0);
              }
              const dateOptions = {
                year: "numeric",
                month: "long",
                day: "numeric"
              };
              return (
                <DiscourseListItem key={discourse.id}>
                  <Pages>
                    pp. {discourse.start_page}-{discourse.end_page}
                  </Pages>
                  <DateComponent title={discourse.date}>
                    {discourse.date &&
                      event.toLocaleDateString("default", dateOptions)}
                  </DateComponent>
                  <Speaker>{discourse.speaker}</Speaker>
                  <Title>
                    <Link
                      to={`/${discourse.volume}/${discourse.start_page}`}
                      dangerouslySetInnerHTML={{
                        __html: prepareTitle(discourse.title)
                      }}
                    />
                  </Title>
                </DiscourseListItem>
              );
            })}
          </DiscourseList>
        </Volume>
      </VolumeWrapper>
    </Layout>
  );
};
<commit_msg>Volume title margin<commit_after>import React from "react";
import { Link } from "gatsby";
import { prepareTitle } from "../common.js";
import Layout from "../components/layout";
import styled from "@emotion/styled";
import SEO from "../components/seo";

const VolumeWrapper = styled.div`
  border-top: 1px solid #faf9f3;
`;

const Volume = styled.div`
  max-width: 960px;
  margin: 0 auto;
  font-size: 0.92rem;
`;

const VolumeTitle = styled.h2`
  @media (max-width: 640px) {
    padding-left: 24px;
    padding-right: 24px;
  }
`;

const DiscourseList = styled.div`
  -moz-border-radius: 2px;
  -webkit-border-radius: 2px;
  border-bottom: 1px solid #e3e1ce;
  border-left: 1px solid #e3e1ce;
  border-spacing: 0;
  border-top: 1px solid #e3e1ce;
  clear: both;
  max-width: 100%;
  background-color: white;
  margin-bottom: 16px;
  @media (max-width: 640px) {
    padding: 12px;
  }
`;

const DiscourseListItem = styled.div`
  display: flex;
  @media (max-width: 640px) {
    flex-direction: column;
    margin-top: 24px;
    margin-bottom: 24px;
  }
  @media (min-width: 641px) {
    margin-top: 8px;
    margin-bottom: 8px;
  }
  &:first-of-type {
    margin-top: 8px;
  }
  &:last-of-type {
    margin-bottom: 8px;
  }
  &:hover {
    background-color: #f8f8f8;
  }
`;

const Pages = styled.div`
  width: 80px;
  padding: 4px 8px;
  @media (max-width: 640px) {
    order: 4;
    padding-left: 24px;
  }
  @media (min-width: 641px) {
    padding-left: 12px;
  }
`;

const DateComponent = styled.div`
  width: 145px;
  padding: 4px 8px;
  @media (max-width: 640px) {
    padding-left: 24px;
    order: 3;
  }
`;

const Speaker = styled.div`
  padding: 4px 8px;
  @media (max-width: 640px) {
    order: 2;
    padding-left: 24px;
  }
  @media (min-width: 641px) {
    width: 130px;
    padding-left: 24px;
  }
`;
const Title = styled.div`
  flex: 1;
  @media (max-width: 640px) {
    order: 1;
  }
  padding: 4px 8px;
  padding-right: 12px;
`;

export default ({
  pageContext: { volumeNumbers, volumeNumber, discourses }
}) => {
  return (
    <Layout volumeNumbers={volumeNumbers} volumeNumber={volumeNumber}>
      <SEO title={`Journal of Discourses, vol. ${volumeNumber}`} />
      <VolumeWrapper>
        <Volume>
          <VolumeTitle>Volume {volumeNumber}</VolumeTitle>
          <DiscourseList>
            {discourses.map(discourse => {
              const event = new Date();
              if (discourse.date) {
                event.setFullYear(parseInt(discourse.date.substr(0, 4), 10));
                event.setMonth(parseInt(discourse.date.substr(5, 7), 10) - 1);
                event.setDate(parseInt(discourse.date.substr(8, 10), 10));
                event.setUTCHours(0);
              }
              const dateOptions = {
                year: "numeric",
                month: "long",
                day: "numeric"
              };
              return (
                <DiscourseListItem key={discourse.id}>
                  <Pages>
                    pp. {discourse.start_page}-{discourse.end_page}
                  </Pages>
                  <DateComponent title={discourse.date}>
                    {discourse.date &&
                      event.toLocaleDateString("default", dateOptions)}
                  </DateComponent>
                  <Speaker>{discourse.speaker}</Speaker>
                  <Title>
                    <Link
                      to={`/${discourse.volume}/${discourse.start_page}`}
                      dangerouslySetInnerHTML={{
                        __html: prepareTitle(discourse.title)
                      }}
                    />
                  </Title>
                </DiscourseListItem>
              );
            })}
          </DiscourseList>
        </Volume>
      </VolumeWrapper>
    </Layout>
  );
};
<|endoftext|>


<commit_before>#!/usr/bin/env node
var yargs = require('yargs');
var Chalk = require('chalk');
var sendError = function(msg){
	msg = '\ntty-table says: ' + msg + '\n';
	console.log(msg);
	process.exit(1);
};

//note that this is the first run
var alreadyRendered = false;
var previousHeight = 0;

//check format of input data
var dataFormat = 'csv' //default

if(yargs.argv.format){
	dataFormat = yargs.argv.format
}

switch(true){
	case(dataFormat.toString().match(/json/i) !== null):
		dataFormat = 'json';
		break;
	default:
		var csv = require('csv');
}

//because diffent dataFormats
var runTable = function(input){

	var header = [],
	body = input,
	//footer = [],
	options = {};

	var Table = require('./public.js');
	var t1 = Table.setup(header,body,options);

	//hide cursor
	console.log('\u001b[?25l');

	//wipe existing if already rendered
	if(alreadyRendered){

		//move cursor up number to the top of the previous print
		//before deleting
		console.log('\u001b['+(previousHeight+3)+'A');

		//delete to end of terminal
		console.log('\u001b[0J');
	}
	else{
		alreadyRendered = true;
	}

	console.log(t1.render());

	//reset the previous height to the height of this output
	//for when we next clear the print
	previousHeight = t1.height;

};

process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(chunk) {

	//handle dataFormats
	switch(true){
		case(dataFormat==='json'):
			try {
				var data = JSON.parse(chunk);
			}
			catch(e){
				var msg = Chalk.bgRed.white(msg);
				msg = msg + "\n\nPlease check to make sure that your input data consists of JSON or specify a different format with the --format flag.";
				sendError(msg);
			}
			runTable(data);
			break;
		default:
			var formatterOptions = {};
			Object.keys(yargs.argv).forEach(function(key){
				if(key.slice(0,4) === 'csv-'){
					formatterOptions[key.slice(4)] = yargs.argv[key];
				}
			});
			csv.parse(chunk,formatterOptions,function(err, data){
				//validate csv
				if(typeof data === 'undefined'){
					var msg = "CSV parse error.";
					msg = Chalk.bgRed.white(msg);
					msg = msg + "\n\nPlease check to make sure that your input data consists of comma separated values or specify a different format with the --format flag.";
					sendError(msg);
				}
				runTable(data);
			});
	}
});

process.stdin.on('end', function() {
	//show cursor
	console.log('\u001b[?25h');
});

//run help only at the end
//yargs.argv.help();
<commit_msg>Support for custom node-csv-parse options via csv-* terminal args<commit_after>#!/usr/bin/env node
var yargs = require('yargs');
yargs.epilog('Copyright github.com/tecfu 2017');
yargs.option('format');
yargs.describe('format','Set input data format');
yargs.choices('format',['json','csv']);
yargs.default('format','csv');

var Chalk = require('chalk');
var sendError = function(msg){
	msg = '\ntty-table error: ' + msg + '\n';
	console.log(msg);
	process.exit(1);
};

//note that this is the first run
var alreadyRendered = false;
var previousHeight = 0;

var dataFormat = 'csv';
switch(true){
	case(yargs.argv.format.toString().match(/json/i) !== null):
		dataFormat = 'json';
		break;
	default:
		var csv = require('csv');
}

//because diffent dataFormats
var runTable = function(input){

	var header = [],
	body = input,
	//footer = [],
	options = {};

	var Table = require('./public.js');
	var t1 = Table.setup(header,body,options);

	//hide cursor
	console.log('\u001b[?25l');

	//wipe existing if already rendered
	if(alreadyRendered){

		//move cursor up number to the top of the previous print
		//before deleting
		console.log('\u001b['+(previousHeight+3)+'A');

		//delete to end of terminal
		console.log('\u001b[0J');
	}
	else{
		alreadyRendered = true;
	}

	console.log(t1.render());

	//reset the previous height to the height of this output
	//for when we next clear the print
	previousHeight = t1.height;

};

process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(chunk) {

	//handle dataFormats
	switch(true){
		case(dataFormat==='json'):
			try {
				var data = JSON.parse(chunk);
			}
			catch(e){
				var msg = Chalk.bgRed.white(msg);
				msg = msg + "\n\nPlease check to make sure that your input data consists of JSON or specify a different format with the --format flag.";
				sendError(msg);
			}
			runTable(data);
			break;
		default:
			var formatterOptions = {};
			Object.keys(yargs.argv).forEach(function(key){
				if(key.slice(0,4) === 'csv-'){
					formatterOptions[key.slice(4)] = yargs.argv[key];
				}
			});
			csv.parse(chunk,formatterOptions,function(err, data){
				//validate csv
				if(typeof data === 'undefined'){
					var msg = "CSV parse error.";
					msg = Chalk.bgRed.white(msg);
					msg = msg + "\n\nPlease check to make sure that your input data consists of comma separated values or specify a different format with the --format flag.";
					sendError(msg);
				}
				runTable(data);
			});
	}
});

process.stdin.on('end', function() {
	//show cursor
	console.log('\u001b[?25h');
});

//run help only at the end
yargs.argv = yargs.help('h').argv;
<|endoftext|>


<commit_before>require('proof')(7, require('cadence')(prove))

function prove (async, assert) {
    var cadence = require('cadence')
    var bin = require('../../reconfigure.bin'), io
    var http = require('http')
    var Semblance = require('semblance')
    var semblance = new Semblance
    var server = http.createServer(semblance.dispatch())
    var exec = require('child_process').exec
    var ip
    if (process.env.DOCKER_HOST) {
        ip = /^[^\d]+([\d.]+)/.exec(process.env.DOCKER_HOST)[1]
    } else {
        console.log(require('os').networkInterfaces())
        ip = require('os').networkInterfaces().eth0.filter(function (iface) {
            return iface.family == 'IPv4'
        })[0].address
    }

    var dock = cadence(function (async) {
        async([function () {
            exec('docker kill reconfigure-etcd', async())
        }, /kill/, function (error) {
            console.log('errorrrrr')
        }], [function () {
            exec('docker rm reconfigure-etcd', async())
        }, /running/, function (error) {
            console.log('errorrrrr')
        }])
    })

    async([function () {
        dock(async())
    }], function () {
            exec('docker run -d -p 4001:4001 -p 2380:2380 -p 2379:2379 \
                 --name reconfigure-etcd quay.io/coreos/etcd \
                 -name reconfigure-etcd \
                 -advertise-client-urls http://' + ip + ':2379,http://' + ip + ':4001 \
                 -listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 \
                 -initial-advertise-peer-urls http://' + ip + ':2380 \
                 -listen-peer-urls http://0.0.0.0:2380 \
                 -initial-cluster-token etcd-cluster-1 \
                 -initial-cluster reconfigure-etcd=http://' + ip + ':2380 \
                 -initial-cluster-state new', async())
    }, function () {
        server.listen(4077, '127.0.0.1', async())
    }, function () {
        io = bin({}, ['serve', '--port=2390', '--etcdaddr=' + ip + ':2379'], {}, async())
    }, function () {
        assert(true, 'running')
        bin({}, ['register', '127.0.0.1:2390', 'http://127.0.0.1:4077'], {}, async())
    }, function (ret) {
        assert(ret.success, true, 'registered')
        bin({}, ['set', '127.0.0.1:2390', 'greeting', 'Hello World!'], {}, async())
    }, function () {
        bin({}, ['set', '127.0.0.1:2390', 'greeting2', 'Hello World!'], {}, async())
        setTimeout(async(), 2500)
    }, function () {
        assert(semblance.shift().body.properties, { greeting: 'Hello World!'},
        'listener updated')
        bin({}, ['list', '127.0.0.1:2390'], {}, async())
    }, function (values) {
        assert((values.indexOf('greeting\tHello World!\n') != -1) &&
        (values.indexOf('greeting2\tHello World!\n') != -1), true, 'key set and retrieved')
       bin({}, ['register', '127.0.0.1:2390', 'blah:4001'], {}, async())
    }, function () {
       bin({}, ['register', '127.0.0.1:2390', 'blah:4001'], {}, async())
    }, function (ret) {
        assert(ret.extant, true, 'duplicant registry')
        bin({}, ['deregister', '127.0.0.1:2390', 'blah:4001'], {}, async())
    }, function (ret) {
        assert(ret.success, true, 'deregistered')
        bin({}, ['registered', '127.0.0.1:2390'], {}, async())
    }, function (ret) {
        assert(ret, 'http://127.0.0.1:4077', 'registry listed')
        io.events.emit('SIGINT')
        server.close()
    })
}
<commit_msg>Cleanup.<commit_after>require('proof')(7, require('cadence')(prove))

function prove (async, assert) {
    var cadence = require('cadence')
    var bin = require('../../reconfigure.bin'), io
    var http = require('http')
    var Semblance = require('semblance')
    var semblance = new Semblance
    var server = http.createServer(semblance.dispatch())
    var exec = require('child_process').exec
    var ip
    if (process.env.DOCKER_HOST) {
        ip = /^[^\d]+([\d.]+)/.exec(process.env.DOCKER_HOST)[1]
    } else {
        console.log(require('os').networkInterfaces())
        ip = require('os').networkInterfaces().eth0.filter(function (iface) {
            return iface.family == 'IPv4'
        })[0].address
    }

    var dock = cadence(function (async) {
        async([function () {
            exec('docker kill reconfigure-etcd', async())
        }, /kill/, function (error) {
        }], [function () {
            exec('docker rm reconfigure-etcd', async())
        }, /running/, function (error) {
        }])
    })

    async([function () {
        dock(async())
    }], function () {
        exec('docker run -d -p 4001:4001 -p 2380:2380 -p 2379:2379 \
             --name reconfigure-etcd quay.io/coreos/etcd \
             -name reconfigure-etcd \
             -advertise-client-urls http://' + ip + ':2379,http://' + ip + ':4001 \
             -listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 \
             -initial-advertise-peer-urls http://' + ip + ':2380 \
             -listen-peer-urls http://0.0.0.0:2380 \
             -initial-cluster-token etcd-cluster-1 \
             -initial-cluster reconfigure-etcd=http://' + ip + ':2380 \
             -initial-cluster-state new', async())
    }, function () {
        server.listen(4077, '127.0.0.1', async())
    }, function () {
        io = bin({}, ['serve', '--port=2390', '--etcdaddr=' + ip + ':2379'], {}, async())
    }, function () {
        assert(true, 'running')
        bin({}, ['register', '127.0.0.1:2390', 'http://127.0.0.1:4077'], {}, async())
    }, function (ret) {
        assert(ret.success, true, 'registered')
        bin({}, ['set', '127.0.0.1:2390', 'greeting', 'Hello World!'], {}, async())
    }, function () {
        bin({}, ['set', '127.0.0.1:2390', 'greeting2', 'Hello World!'], {}, async())
        setTimeout(async(), 2500)
    }, function () {
        assert(semblance.shift().body.properties, { greeting: 'Hello World!'},
        'listener updated')
        bin({}, ['list', '127.0.0.1:2390'], {}, async())
    }, function (values) {
        assert((values.indexOf('greeting\tHello World!\n') != -1) &&
        (values.indexOf('greeting2\tHello World!\n') != -1), true, 'key set and retrieved')
       bin({}, ['register', '127.0.0.1:2390', 'blah:4001'], {}, async())
    }, function () {
       bin({}, ['register', '127.0.0.1:2390', 'blah:4001'], {}, async())
    }, function (ret) {
        assert(ret.extant, true, 'duplicant registry')
        bin({}, ['deregister', '127.0.0.1:2390', 'blah:4001'], {}, async())
    }, function (ret) {
        assert(ret.success, true, 'deregistered')
        bin({}, ['registered', '127.0.0.1:2390'], {}, async())
    }, function (ret) {
        assert(ret, 'http://127.0.0.1:4077', 'registry listed')
        io.events.emit('SIGINT')
        server.close()
    })
}
<|endoftext|>


<commit_before>/*
 * grunt-documentation
 * https://github.com/documentationjs/grunt-documentation
 *
 * Copyright (c) 2015 André Fiedler
 * Licensed under the MIT license.
 */
'use strict';

module.exports = function(grunt) {

    var path = require('path'),
        chalk = require('chalk'),
        documentation = require('documentation');

    grunt.registerMultiTask('documentation', 'Use Grunt with documentation to generate great documentation for your JavaScript projects.', function() {
        var options = this.options({
            format: 'html',
            shallow: false,
            github: false,
            access: ['public', 'protected', 'undefined'],
            order: []
        });

        var formatter = documentation.formats[options.format];
        if (!formatter) {
            throw new Error('invalid format given: valid options are ' + Object.keys(documentation.formats).join(', '));
        }

        var docOptions = {
            github: options.github,
            shallow: options.shallow,
            access: options.access,
            order: options.order
        };

        var done = this.async(),
            waiting = 0;

        function await (c) {
            c = c || 1;
            waiting += c;
        }

        function release() {
            waiting--;
            if (waiting <= 0) {
                done();
                grunt.log.writeln(chalk.green('Done, created documentation at ') + path.join(process.cwd(), options.destination));
            }
        }

        function generateDocs(files) {
            await ();
            documentation(files, docOptions, function(err, comments) {
                if (err) {
                    grunt.log.error(err.toString());
                    if (err.codeFrame) {
                        grunt.log.error(err.codeFrame);
                    }
                    done(false);
                } else {
                    await ();
                    formatter(comments, {}, function(err, output) {
                        if (err) {
                            grunt.log.error(err.toString());
                            if (err.codeFrame) {
                                grunt.log.error(err.codeFrame);
                            }
                            done(false);
                        } else {
                            if (options.format === 'json' || options.format === 'md') {
                                var dest = path.join(process.cwd(), options.destination, (options.filename || 'API.' + options.format));
                                grunt.file.write(dest, output);
                                grunt.log.writeln('File ' + chalk.cyan(options.filename || 'API.' + options.format) + ' created.');
                            } else if (options.format === 'html') {
                                await (output.length);
                                output.forEach(function(file) {
                                    var dest = path.join(process.cwd(), options.destination, file.relative);
                                    if (file.isDirectory() || grunt.file.isDir(file.path)) {
                                        grunt.file.mkdir(dest);
                                        grunt.verbose.writeln('Directory ' + chalk.cyan(file.relative) + ' created.');
                                    } else {
                                        grunt.file.write(dest, file.contents);
                                        grunt.verbose.writeln('File ' + chalk.cyan(file.relative) + ' created.');
                                    }
                                    release();
                                });
                            }
                        }
                        release();
                    });
                }
                release();
            });
        }

        var files = [];

        var filesToProcess = this.files.length;

        this.files.forEach(function(f) {
            var src = f.src.filter(function(filepath) {
                // Warn on and remove invalid source files (if nonull was set).
                if (!grunt.file.exists(filepath)) {
                    grunt.log.warn('Source file "' + filepath + '" not found.');
                    return false;
                } else {
                    return true;
                }
            });
            files = files.concat(src);
            filesToProcess--;
            if (filesToProcess <= 0) {
                generateDocs(files);
            }
        }.bind(this));
    });

};
<commit_msg>Adds theme, name and version to options<commit_after>/*
 * grunt-documentation
 * https://github.com/documentationjs/grunt-documentation
 *
 * Copyright (c) 2015 André Fiedler
 * Licensed under the MIT license.
 */
'use strict';

module.exports = function(grunt) {

    var path = require('path'),
        chalk = require('chalk'),
        documentation = require('documentation');

    grunt.registerMultiTask('documentation', 'Use Grunt with documentation to generate great documentation for your JavaScript projects.', function() {
        var options = this.options({
            format: 'html',
            shallow: false,
            github: false,
            access: ['public', 'protected', 'undefined'],
            order: []
        });

        var formatter = documentation.formats[options.format];
        if (!formatter) {
            throw new Error('invalid format given: valid options are ' + Object.keys(documentation.formats).join(', '));
        }

        var docOptions = {
            github: options.github,
            shallow: options.shallow,
            access: options.access,
            order: options.order,
            theme: options.theme,
            name: options.name,
            version: options.version
        };

        var done = this.async(),
            waiting = 0;

        function await (c) {
            c = c || 1;
            waiting += c;
        }

        function release() {
            waiting--;
            if (waiting <= 0) {
                done();
                grunt.log.writeln(chalk.green('Done, created documentation at ') + path.join(process.cwd(), options.destination));
            }
        }

        function generateDocs(files) {
            await ();
            documentation(files, docOptions, function(err, comments) {
                if (err) {
                    grunt.log.error(err.toString());
                    if (err.codeFrame) {
                        grunt.log.error(err.codeFrame);
                    }
                    done(false);
                } else {
                    await ();
                    formatter(comments, docOptions, function(err, output) {
                        if (err) {
                            grunt.log.error(err.toString());
                            if (err.codeFrame) {
                                grunt.log.error(err.codeFrame);
                            }
                            done(false);
                        } else {
                            if (options.format === 'json' || options.format === 'md') {
                                var dest = path.join(process.cwd(), options.destination, (options.filename || 'API.' + options.format));
                                grunt.file.write(dest, output);
                                grunt.log.writeln('File ' + chalk.cyan(options.filename || 'API.' + options.format) + ' created.');
                            } else if (options.format === 'html') {
                                await (output.length);
                                output.forEach(function(file) {
                                    var dest = path.join(process.cwd(), options.destination, file.relative);
                                    if (file.isDirectory() || grunt.file.isDir(file.path)) {
                                        grunt.file.mkdir(dest);
                                        grunt.verbose.writeln('Directory ' + chalk.cyan(file.relative) + ' created.');
                                    } else {
                                        grunt.file.write(dest, file.contents);
                                        grunt.verbose.writeln('File ' + chalk.cyan(file.relative) + ' created.');
                                    }
                                    release();
                                });
                            }
                        }
                        release();
                    });
                }
                release();
            });
        }

        var files = [];

        var filesToProcess = this.files.length;

        this.files.forEach(function(f) {
            var src = f.src.filter(function(filepath) {
                // Warn on and remove invalid source files (if nonull was set).
                if (!grunt.file.exists(filepath)) {
                    grunt.log.warn('Source file "' + filepath + '" not found.');
                    return false;
                } else {
                    return true;
                }
            });
            files = files.concat(src);
            filesToProcess--;
            if (filesToProcess <= 0) {
                generateDocs(files);
            }
        }.bind(this));
    });

};
<|endoftext|>