<commit_before>var get = Ember.get, set = Ember.set, Copyable = Ember.Copyable, computed = Ember.computed,
    cacheFor = Ember.cacheFor,
    cacheGet = cacheFor.get,
    metaFor = Ember.meta;

import ModelSet from '../collections/model_set';

var Model = Ember.Object.extend(Copyable, {

  id: null,
  clientId: null,
  rev: null,
  clientRev: 0,
  session: null,
  errors: null,
  isModel: true,
  isDeleted: false,

  /**
    Two models are "equal" when they correspond to the same
    key. This does not mean they necessarily have the same data.
  */
  isEqual: function(model) {
    if(!model) return false;
    var clientId = get(this, 'clientId');
    var otherClientId = get(model, 'clientId');
    if(clientId && otherClientId) {
      return clientId === otherClientId;
    }
    // in most cases clientIds will always be set, however
    // during serialization this might not be the case
    var id = get(this, 'id');
    var otherId = get(model, 'id');
    return this.isSameType(model) && id === otherId
  },

  isSameType: function(model) {
    return this.hasType(get(model, 'type'));
  },

  /**
    Model promises are just proxies and do not have the
    literal type of their contents.
  */
  hasType: function(type) {
    return get(this, 'type').detect(type);
  },

  type: computed(function(key, value) {
    return value || this.constructor;
  }),

  typeKey: computed(function() {
    return get(this, 'type.typeKey');
  }),

  toStringExtension: function() {
    return "[" + get(this, 'id') + ", " + get(this, 'clientId') + "]";
  },

  lazyCopy: function() {
    var type = get(this, 'type');
    return type.create({
      id: get(this, 'id'),
      clientId: get(this, 'clientId'),
      isDeleted: get(this, 'isDeleted'),
      errors: get(this, 'errors')
    });
  },

  // these properties are volatile so they don't trigger lazy loads
  // on promises by calling `willWatchProperty` on their dependencies
  hasErrors: computed(function() {
    return !!get(this, 'errors');
  }).volatile(),

  isDetached: computed(function() {
    return !get(this, 'session');
  }).volatile(),

  isManaged: computed(function() {
    return !!get(this, 'session');
  }).volatile(),

  isNew: computed(function() {
    return !get(this, 'id');
  }).property('id'),

  /**
    Whether the model is dirty or not.

    Logically, this corresponds to whether any properties of the
    model have been set since the last flush.
    @property isDirty
  */
  isDirty: computed(function() {
    var session = get(this, 'session');
    if(!session) return false;
    return get(session, 'dirtyModels').contains(this);
  }).volatile(),

  // creates a shallow copy with lazy children
  // TODO: we should not lazily copy detached children
  copy: function() {
    var dest = this.constructor.create();
    this.copyTo(dest);
    return dest;
  },

  copyTo: function(dest) {
    dest.beginPropertyChanges();
    this.copyAttributes(dest);
    this.copyMeta(dest);
    this.eachLoadedRelationship(function(name, relationship) {
      if(relationship.kind === 'belongsTo') {
        var child = get(this, name);
        set(dest, name, child && child.lazyCopy());
      } else if(relationship.kind === 'hasMany') {
        var children = get(this, name);
        var destChildren = [];
        children.forEach(function(child) {
          destChildren.pushObject(child.lazyCopy());
        });
        set(dest, name, destChildren);
      }
    }, this);
    dest.endPropertyChanges();
    return dest;
  },

  copyAttributes: function(dest) {
    dest.beginPropertyChanges();

    this.eachLoadedAttribute(function(name, meta) {
      var left = get(this, name);
      var right = get(dest, name);
      var copy;
      // Ember.copy does not support Date
      if(left instanceof Date) {
        copy = new Date(left.getTime());
      } else {
        copy = Ember.copy(left, true);
      }
      set(dest, name, copy);
    }, this);
    dest.endPropertyChanges();
  },

  copyMeta: function(dest) {
    set(dest, 'id', get(this, 'id'));
    set(dest, 'clientId', get(this, 'clientId'));
    set(dest, 'rev', get(this, 'rev'));
    set(dest, 'clientRev', get(this, 'clientRev'));
    set(dest, 'errors', Ember.copy(get(this, 'errors')));
    set(dest, 'isDeleted', get(this, 'isDeleted'));
  },

  willWatchProperty: function(key) {
    if(get(this, 'isManaged') && this.shouldTriggerLoad(key)) {
      Ember.run.scheduleOnce('actions', this, this.load);
    }
  },

  shouldTriggerLoad: function(key) {
    return this.isAttributeOrRelationship(key) && !this.isPropertyLoaded(key);
  },

  isAttributeOrRelationship: function(key) {
    var proto = this.constructor.proto(),
        descs = Ember.meta(proto).descs,
        desc = descs[key],
        meta = desc && desc._meta;

    return meta && (meta.isAttribute || meta.isRelationship);
  },

  isPropertyLoaded: function(key) {
    if(get(this, 'isNew')) {
      return true;
    }

    var proto = this.constructor.proto(),
        descs = Ember.meta(proto).descs,
        desc = descs[key],
        meta = desc && desc._meta;

    if(meta.isRelationship && meta.kind === 'belongsTo') {
      return typeof this['__' + key] !== 'undefined';
    }

    var meta = metaFor(this),
        cache = meta.cache,
        cached = cacheGet(cache, key);

    return typeof cached !== 'undefined';
  },

  anyPropertiesLoaded: function() {
    var result = false;
    get(this, 'type.fields').forEach(function(name, meta) {
      result = result || this.isPropertyLoaded(name);
    }, this);
    return result;
  },

  load: sessionAlias('loadModel'),
  refresh: sessionAlias('refresh'),
  deleteModel: sessionAlias('deleteModel'),
  remoteCall: sessionAlias('remoteCall'),
  markClean: sessionAlias('markClean'),
  invalidate: sessionAlias('invalidate'),
  touch: sessionAlias('touch')

});

Model.reopenClass({

  /**
    This is the only static method implemented in order to play nicely
    with Ember's default model conventions in the router. It is preferred
    to explicitly call `load` on a session.

    In order to use this method, you must set the Ep.__container__ property. E.g.

    ```
      Ep.__container__ = App.__container__;
    ```
  */
  find: function(id) {
    if(!Ep.__container__) {
      throw new Ember.Error("The Ep.__container__ property must be set in order to use static find methods.");
    }
    var container = Ep.__container__;
    var session = container.lookup('session:main');
    return session.find(this, id);
  },

  typeKey: computed(function() {
    var camelized = this.toString().split(/[:.]/)[1];
    if(camelized) {
      return Ember.String.underscore(camelized);
    } else {
      throw new Ember.Error("Could not infer typeKey for " + this.toString());
    }
  })

});

function sessionAlias(name) {
  return function () {
    var session = get(this, 'session');
    Ember.assert("Cannot call " + name + " on a detached model", session);
    var args = [].splice.call(arguments,0);
    args.unshift(this);
    return session[name].apply(session, args);
  };
}

export default Model;
<commit_msg>wip: es6ify models<commit_after>var get = Ember.get, set = Ember.set, Copyable = Ember.Copyable, computed = Ember.computed,
    cacheFor = Ember.cacheFor,
    cacheGet = cacheFor.get,
    metaFor = Ember.meta;

import BaseClass from '../utils/base_class';
import ModelSet from '../collections/model_set';

export default class Model extends BaseClass {

var Model = Ember.Object.extend(Copyable, {

  id: null,
  clientId: null,
  rev: null,
  clientRev: 0,
  session: null,
  errors: null,
  isModel: true,
  isDeleted: false,

  /**
    Two models are "equal" when they correspond to the same
    key. This does not mean they necessarily have the same data.
  */
  isEqual: function(model) {
    if(!model) return false;
    var clientId = get(this, 'clientId');
    var otherClientId = get(model, 'clientId');
    if(clientId && otherClientId) {
      return clientId === otherClientId;
    }
    // in most cases clientIds will always be set, however
    // during serialization this might not be the case
    var id = get(this, 'id');
    var otherId = get(model, 'id');
    return this.isSameType(model) && id === otherId
  },

  isSameType: function(model) {
    return this.hasType(get(model, 'type'));
  },

  /**
    Model promises are just proxies and do not have the
    literal type of their contents.
  */
  hasType: function(type) {
    return get(this, 'type').detect(type);
  },

  type: computed(function(key, value) {
    return value || this.constructor;
  }),

  typeKey: computed(function() {
    return get(this, 'type.typeKey');
  }),

  toStringExtension: function() {
    return "[" + get(this, 'id') + ", " + get(this, 'clientId') + "]";
  },

  lazyCopy: function() {
    var type = get(this, 'type');
    return type.create({
      id: get(this, 'id'),
      clientId: get(this, 'clientId'),
      isDeleted: get(this, 'isDeleted'),
      errors: get(this, 'errors')
    });
  },

  // these properties are volatile so they don't trigger lazy loads
  // on promises by calling `willWatchProperty` on their dependencies
  hasErrors: computed(function() {
    return !!get(this, 'errors');
  }).volatile(),

  isDetached: computed(function() {
    return !get(this, 'session');
  }).volatile(),

  isManaged: computed(function() {
    return !!get(this, 'session');
  }).volatile(),

  isNew: computed(function() {
    return !get(this, 'id');
  }).property('id'),

  /**
    Whether the model is dirty or not.

    Logically, this corresponds to whether any properties of the
    model have been set since the last flush.
    @property isDirty
  */
  isDirty: computed(function() {
    var session = get(this, 'session');
    if(!session) return false;
    return get(session, 'dirtyModels').contains(this);
  }).volatile(),

  // creates a shallow copy with lazy children
  // TODO: we should not lazily copy detached children
  copy: function() {
    var dest = this.constructor.create();
    this.copyTo(dest);
    return dest;
  },

  copyTo: function(dest) {
    dest.beginPropertyChanges();
    this.copyAttributes(dest);
    this.copyMeta(dest);
    this.eachLoadedRelationship(function(name, relationship) {
      if(relationship.kind === 'belongsTo') {
        var child = get(this, name);
        set(dest, name, child && child.lazyCopy());
      } else if(relationship.kind === 'hasMany') {
        var children = get(this, name);
        var destChildren = [];
        children.forEach(function(child) {
          destChildren.pushObject(child.lazyCopy());
        });
        set(dest, name, destChildren);
      }
    }, this);
    dest.endPropertyChanges();
    return dest;
  },

  copyAttributes: function(dest) {
    dest.beginPropertyChanges();

    this.eachLoadedAttribute(function(name, meta) {
      var left = get(this, name);
      var right = get(dest, name);
      var copy;
      // Ember.copy does not support Date
      if(left instanceof Date) {
        copy = new Date(left.getTime());
      } else {
        copy = Ember.copy(left, true);
      }
      set(dest, name, copy);
    }, this);
    dest.endPropertyChanges();
  },

  copyMeta: function(dest) {
    set(dest, 'id', get(this, 'id'));
    set(dest, 'clientId', get(this, 'clientId'));
    set(dest, 'rev', get(this, 'rev'));
    set(dest, 'clientRev', get(this, 'clientRev'));
    set(dest, 'errors', Ember.copy(get(this, 'errors')));
    set(dest, 'isDeleted', get(this, 'isDeleted'));
  },

  willWatchProperty: function(key) {
    if(get(this, 'isManaged') && this.shouldTriggerLoad(key)) {
      Ember.run.scheduleOnce('actions', this, this.load);
    }
  },

  shouldTriggerLoad: function(key) {
    return this.isAttributeOrRelationship(key) && !this.isPropertyLoaded(key);
  },

  isAttributeOrRelationship: function(key) {
    var proto = this.constructor.proto(),
        descs = Ember.meta(proto).descs,
        desc = descs[key],
        meta = desc && desc._meta;

    return meta && (meta.isAttribute || meta.isRelationship);
  },

  isPropertyLoaded: function(key) {
    if(get(this, 'isNew')) {
      return true;
    }

    var proto = this.constructor.proto(),
        descs = Ember.meta(proto).descs,
        desc = descs[key],
        meta = desc && desc._meta;

    if(meta.isRelationship && meta.kind === 'belongsTo') {
      return typeof this['__' + key] !== 'undefined';
    }

    var meta = metaFor(this),
        cache = meta.cache,
        cached = cacheGet(cache, key);

    return typeof cached !== 'undefined';
  },

  anyPropertiesLoaded: function() {
    var result = false;
    get(this, 'type.fields').forEach(function(name, meta) {
      result = result || this.isPropertyLoaded(name);
    }, this);
    return result;
  },

});

Model.reopen({
  load: sessionAlias('loadModel'),
  refresh: sessionAlias('refresh'),
  deleteModel: sessionAlias('deleteModel'),
  remoteCall: sessionAlias('remoteCall'),
  markClean: sessionAlias('markClean'),
  invalidate: sessionAlias('invalidate'),
  touch: sessionAlias('touch')
});

Model.reopenClass({

  /**
    This is the only static method implemented in order to play nicely
    with Ember's default model conventions in the router. It is preferred
    to explicitly call `load` on a session.

    In order to use this method, you must set the Ep.__container__ property. E.g.

    ```
      Ep.__container__ = App.__container__;
    ```
  */
  find: function(id) {
    if(!Ep.__container__) {
      throw new Ember.Error("The Ep.__container__ property must be set in order to use static find methods.");
    }
    var container = Ep.__container__;
    var session = container.lookup('session:main');
    return session.find(this, id);
  },

  typeKey: computed(function() {
    var camelized = this.toString().split(/[:.]/)[1];
    if(camelized) {
      return Ember.String.underscore(camelized);
    } else {
      throw new Ember.Error("Could not infer typeKey for " + this.toString());
    }
  })

});

function sessionAlias(name) {
  return function () {
    var session = get(this, 'session');
    Ember.assert("Cannot call " + name + " on a detached model", session);
    var args = [].splice.call(arguments,0);
    args.unshift(this);
    return session[name].apply(session, args);
  };
}
<|endoftext|>


<commit_before>/**
 * Static data the for the transaction form
 */

export default function(groupid) {
  if(typeof groupid == "string")
    groupid = parseInt(groupid, 10);

  let tags = [];
  switch (groupid) {
    // Women Who Code
    case 2:
    case 3:
    case 4:
      tags = [
        'Donation',
        'Event Refreshments',
        'Event Travel',
        'Event Prizes',
        'Event Facilities',
        'Network supplies',
        'Other Program Expense'
      ];
      break;
    default:
      tags = [
        'Communications',
        'Design',
        'Donation',
        'Engineering',
        'Food & Beverage',
        'Marketing',
        'Legal',
        'Supplies & materials',
        'Travel',
        'Team',
        'Office',
        'Other',
        'Web services'
      ];
  }
  return tags.sort();
}
<commit_msg>Added custom categories for LaPrimaire<commit_after>/**
 * Static data the for the transaction form
 */

export default function(groupid) {
  if(typeof groupid == "string")
    groupid = parseInt(groupid, 10);

  let tags = [];
  switch (groupid) {
    // Women Who Code
    case 2:
    case 3:
    case 4:
      tags = [
        'Donation',
        'Event Refreshments',
        'Event Travel',
        'Event Prizes',
        'Event Facilities',
        'Network supplies',
        'Other Program Expense'
      ];
      break;
    case 6:
      tags = [
        'Admin',
        'Autre',
        'Communication',
        'Déplacement',
        'Marketing',
        'NDD',
        'Outils',
        'PI',
        'Papeterie',
        'Représentation',
        'Serveur',
        'Transport'
      ];
      break;
    default:
      tags = [
        'Communications',
        'Design',
        'Donation',
        'Engineering',
        'Food & Beverage',
        'Marketing',
        'Legal',
        'Supplies & materials',
        'Travel',
        'Team',
        'Office',
        'Other',
        'Web services'
      ];
  }
  return tags.sort();
}
<|endoftext|>


<commit_before>import Ember from 'ember'
const {run} = Ember
import {afterEach, beforeEach, describe, it} from 'mocha'
import {expect} from 'chai'
import startApp from '../helpers/start-app'

describe('Acceptance: Application', function () {
  let application

  beforeEach(function () {
    application = startApp()
  })

  afterEach(function () {
    run(application, 'destroy')
  })

  it('can visit /', function () {
    visit('/')

    andThen(function () {
      expect(currentPath()).to.equal('demo')
    })
  })
  it('can visit /palette', function () {
    visit('/palette')

    andThen(function () {
      expect(currentPath()).to.equal('palette')
    })
  })
  it('can visit /typography', function () {
    visit('/typography')

    andThen(function () {
      expect(currentPath()).to.equal('typography')
    })
  })
  it('can visit /icons', function () {
    visit('/icons')

    andThen(function () {
      expect(currentPath()).to.equal('icons')
    })
  })
  it('can visit /area', function () {
    visit('/area')

    andThen(function () {
      expect(currentPath()).to.equal('area')
    })
  })
  it('can visit /button', function () {
    visit('/button')

    andThen(function () {
      expect(currentPath()).to.equal('button')
    })
  })
  it('can visit /checkbox', function () {
    visit('/checkbox')

    andThen(function () {
      expect(currentPath()).to.equal('checkbox')
    })
  })
  it('can visit /field', function () {
    visit('/field')

    andThen(function () {
      expect(currentPath()).to.equal('field')
    })
  })
  it('can visit /password', function () {
    visit('/password')

    andThen(function () {
      expect(currentPath()).to.equal('password')
    })
  })
  it('can visit /layout', function () {
    visit('/layout')

    andThen(function () {
      expect(currentPath()).to.equal('layout')
    })
  })
  it('can visit /link', function () {
    visit('/link')

    andThen(function () {
      expect(currentPath()).to.equal('link.index')
    })
  })
})
<commit_msg>Improve acceptance test<commit_after>import {afterEach, beforeEach, describe, it} from 'mocha'
import {expect} from 'chai'
import destroyApp from '../helpers/destroy-app'
import startApp from '../helpers/start-app'

describe('Acceptance: Application', function () {
  let application

  beforeEach(function () {
    application = startApp()
  })

  afterEach(function () {
    destroyApp(application)
  })

  describe('visit /', function () {
    beforeEach(function () {
      return visit('/')
    })

    it('redirects to /demo', function () {
      expect(currentPath()).to.equal('demo')
    })
  })

  ;[
    'area',
    'button',
    'checkbox',
    'field',
    'icons',
    'layout',
    'palette',
    'password',
    'typography'
  ]
    .forEach((path) => {
      describe(`visit /${path}`, function () {
        beforeEach(function () {
          return visit(`/${path}`)
        })

        it('does not redirect', function () {
          expect(currentPath()).to.equal(path)
        })
      })
    })

  describe('visit /link', function () {
    beforeEach(function () {
      return visit('/link')
    })

    it('renders correct route', function () {
      expect(currentPath()).to.equal('link.index')
    })
  })
})
<|endoftext|>


<commit_before>// @flow

import PropTypes from 'prop-types';
import checkPropTypes from 'check-prop-types';
import forIn from 'lodash/forIn';
import isEmpty from 'lodash/isEmpty';
import isEqual from 'lodash/isEqual';
import groupBy from 'lodash/groupBy';
import map from 'lodash/map';

import type { dataType, orderType, filterType, paginateType } from './data.type';

const DIRECTIONS = ['asc', 'desc'];
const FILTER_SCHEMA = {
  id: PropTypes.string.isRequired,
  handler: PropTypes.func.isRequired,
  arguments: PropTypes.arrayOf(PropTypes.string),
  status: PropTypes.bool.isRequired,
};

/**
 * Helper to Data grid
 * Object transforms: paginate, sort, filters
 *
 * @since 1.0.0
 * @class
 * @param  {Object} data     full data Object
 * @param  {Object} [settings]
 * @example
 * // data
 * [
 *   { id: 1, name: 'Megan J. Cushman', gender: 1, visits: '2017-07-23' },
 *   { id: 2, name: 'Taylor R. Fallin', gender: 2, visits: '2017-07-22' },
 *   { id: 3, name: 'Jose C. Rosado', gender: 1, visits: '2017-07-20' },
 *   { id: 4, name: 'Sammy C. Brandt', gender: 1, visits: '2017-07-10' },
 * ];
 *
 * // settings
 *
 * {
 *   paginate:
 *   {
 *     limit: 2,
 *   },
 *   order:
 *   {
 *     column: 'name',
 *     direction: 'desc',
 *   },
 *   filters:
 *   [
 *     {
 *       id: 'search',
 *       handler: (record, value) =>
 *         record.name
 *           .toString()
 *           .toLowerCase()
 *           .indexOf(value.toString().toLowerCase()) >= 0,
 *       arguments: [],
 *       status: false,
 *     },
 *   ],
 * };
 *
 * new Data(data, settings);
 */
class Data
{
  _data: dataType;
  _results: dataType = [];
  _order: orderType;
  _filters: filterType[];
  _paginate: paginateType;

  /**
   * @constructs
   * @private
   * @param  {Object} data     full data Object
   * @param  {Object} [settings]
   */
  constructor(
    data: dataType,
    settings?: {
      order?: orderType,
      filters?: Array<filterType>,
      paginate?: paginateType,
    } = {},
  )
  {
    if (data === undefined || typeof settings !== typeof {})
    {
      return false;
    }

    this._data = data;

    this._results = this._data.concat([]);

    this._order =
    {
      column: '',
      direction: 'asc', // asc || desc
    };

    this._filters = [];

    this._paginate =
    {
      page: 1,
      limit: 20,
      totalPage: 0,
      results: null,
      nextPage: null,
      prevPage: null,
    };

    this.order = settings.order || {};
    this.filters = settings.filters || [];
    this.paginate = settings.paginate || { page: 1 };
  }

  /* !- Getter Setter */

  /**
   * Raw data.
   * @type {array}
   */
  get data(): dataType
  {
    return this._data;
  }

  set data(data: dataType)
  {
    this._data = data;
    this.handle();
  }

  /**
   * Handled data results.
   * @type {array}
   */
  get results(): dataType
  {
    return this._results;
  }

  set results(results: dataType)
  {
    this._results = results;
  }

  /**
   * Current order settings
   * @type {object}
   * @example
   *
   * const data = new Data(json);
   *
   * data.order = {column: 'name', direction: 'asc'};
   * // => data.results
   */
  get order(): orderType
  {
    return this._order;
  }

  set order(settings: orderType)
  {
    if (isEmpty(settings))
    {
      return;
    }

    const order = Object.assign({}, this._order);

    if (!settings.direction && settings.column === order.column)
    {
      order.direction = DIRECTIONS[+!DIRECTIONS.indexOf(order.direction)];
    }
    else if (
      settings.direction !== this.order.direction
      && DIRECTIONS.indexOf(settings.direction) !== -1
    )
    {
      order.direction = settings.direction;
    }

    if (!order.direction)
    {
      order.direction = DIRECTIONS[0];
    }

    if (settings.column !== order.column &&
        Object.keys(this.data[0]).indexOf(settings.column) !== -1)
    {
      order.column = settings.column;
    }

    if (!isEqual(order, this.order))
    {
      this._order = order;
      this.handle();
    }
  }

  /**
   * Filter Collections
   * @type {array}
   * @example
   *
   * const data = new Data(json);
   *
   * data.filters = [
   * {
   *    id: 'foo',
   *    handler: (record, foo, bar) => record.foo == foo,
   *    arguments: ['foo', 'bar'],
   *    status: true,
   * }];
   *
   * // => data.results
   */
  get filters(): filterType[]
  {
    return this._filters;
  }

  set filters(newFilters: filterType[] | filterType)
  {
    const filters = (Array.isArray(newFilters)) ?
      newFilters : [newFilters];

    let modified = false;

    filters.forEach((filter) =>
    {
      const filterIndex = this.filters.findIndex(f => f.id === filter.id);

      if (filterIndex === -1)
      {
        modified = this._registerNewFilter(filter);
      }
      else if (!filter.arguments || isEmpty(filter.arguments))
      {
        if (this._inactiveFilterByIndex(filterIndex))
        {
          modified = true;
        }
      }
      else if (this._updateFilterByIndex(filterIndex, filter))
      {
        modified = true;
      }
    });

    if (modified)
    {
      this.handle();
    }
  }

  /**
   * Current order settings
   * @type {object}
   * @example
   *
   * const data = new Data(json);
   *
   * data.paginate = {
   *    page: 3,
   *    limit: 1
   * };
   *
   * // => data.paginate.results
   */
  get paginate(): paginateType
  {
    return this._paginate;
  }

  set paginate(options: paginateType)
  {
    if (!isEmpty(options))
    {
      this._paginate = this._pagination(options);
    }
  }

  /* !- Private methods */

  /**
   * Pagination
   *
   * @private
   * @return {void}
   */
  _pagination(options: paginateType): paginateType
  {
    if (typeof options !== 'object')
    {
      return this._paginate;
    }

    const paginate = Object.assign({}, this._paginate, options);

    if (typeof options.limit === 'number')
    {
      paginate.limit = options.limit;
    }

    paginate.total = this._results.length;
    paginate.totalPage = Math.ceil(paginate.total / paginate.limit);

    if (typeof options.page === 'number' &&
        options.page > 0 &&
        options.page <= paginate.totalPage)
    {
      paginate.page = Math.ceil(options.page);
    }
    else
    {
      paginate.page = this._paginate.page;
    }

    const offset = (paginate.page - 1) * paginate.limit;

    paginate.results = this._results.slice(offset, offset + paginate.limit);
    paginate.nextPage = (paginate.page < paginate.totalPage)
      ? paginate.page + 1 : null;
    paginate.prevPage = (paginate.page > 1)
      ? paginate.page - 1 : null;

    return paginate;
  }

  /**
   * Sorting _results object by key (alias column) and directions
   * this.order = {column: 'name', direction: 'desc'}
   *
   * if this.order.column is null method not apply.
   *
   * @private
   * @return {void}
   */
  _sort()
  {
    if (this.order.column)
    {
      this._results.sort((recordOne, recordTwo) =>
      {
        const a = recordOne[this.order.column] || '';
        const b = recordTwo[this.order.column] || '';

        // number
        if (!isNaN(a) && !isNaN(b))
        {
          return +(a > b);
        }

        // string
        return a.toLowerCase().localeCompare(b.toLowerCase());
      });

      if (this.order.direction !== 'asc')
      {
        this._results.reverse();
      }
    }
  }

  /**
   * Apply registered filters
   *
   * @private
   * @return {void}
   */
  _filter()
  {
    this._results = this._results.filter((record) =>
    {
      let proper = true;

      this._filters
        .map((filter) =>
        {
          if (proper && filter.status && !filter.handler(record, ...filter.arguments))
          {
            proper = false;
          }

          return true;
        });

      return proper;
    });
  }

  /**
   * Push new filter object to _filters
   * if it is corresponds to scheme
   *
   * @private
   * @param  {object} filter
   * @return {boolean} successfull of registration
   */
  _registerNewFilter(filter: filterType): boolean
  {
    const result = checkPropTypes(
      FILTER_SCHEMA,
      filter,
      'filter',
      'Data',
    );

    if (result)
    {
      return false;
    }

    this._filters.push(filter);
    return true;
  }

  /**
   * Inactivate filter by index of _filters
   *
   * @private
   * @param  {int} index
   * @return {bool} true
   */
  _inactiveFilterByIndex(index: number): boolean
  {
    if (!this._filters[index])
    {
      return false;
    }

    this._filters[index].status = false;
    return true;
  }

  /**
   * Update current filter property
   *
   * @private
   * @param  {int} index
   * @param  {object} filter new filter properties
   * @return {boolean} successfull of update process
   */
  _updateFilterByIndex(index: number, filter: filterType): boolean
  {
    let modified = false;
    const newFilter = this._filters[index];

    forIn(newFilter, (value, key) =>
    {
      if (typeof filter[key] === typeof value)
      {
        newFilter[key] = filter[key];
        modified = true;
      }
    });

    if (modified)
    {
      this._filters[index] = newFilter;
      return true;
    }

    return false;
  }

  /**
   * Remove current filter (oposite registerFilter)
   *
   * @private
   * @param  {int} index
   * @return {boolean}
   * @todo not used yet
   */
  // _terminateFilterByIndex(index)
  // {
  //   return true;
  // }

  /* !- Public methods */

  /**
  * Data handling process -> this.results
  * - apply filters
  * - sort results
  *
  * @return {void}
  */
  handle(): void
  {
    this._results = this._data.concat([]);

    if (!isEmpty(this.filters))
    {
      this._filter();
    }

    this._sort();

    this._paginate = this._pagination(this._paginate);
  }

  /**
   * Return data element by index
   * @param  {int} index
   * @return {object}
   */
  getDataByIndex(index: number): {} | boolean
  {
    if (typeof this._data[index] === 'undefined')
    {
      return false;
    }

    return this.data[index];
  }

  /**
   * Return results element by index
   * @param  {int} index
   * @return {object}
   */
  getResultByIndex(index: number): {} | boolean
  {
    if (typeof this.results[index] === 'undefined')
    {
      return false;
    }

    return this.results[index];
  }

  /**
  * It returns grouped results
  * @param  {string} field    grouping result by this object key
  * @param  {function} [valueIteratee = (records, field) => records.length]
  * group field items method. Default is count group elements
  *
  * @param  {function} [labelIteratee = (records, field) => field]
  * group field value format method. Default return original value
  *
  * @example
  * <caption>Grouping by date, where the value sum every record person property
  * [{date: '2016-02-03', person: '3'}...]</caption>
  * // return {'2016-02-03': 22, '2016-02-04': 25 }
  * groupByResults('date', (value, key) => (_.sum(_.map(value, i => parseInt(i.person)))))
  *
  * // grouping by category where the value max id each group
  *
  * dataModel.getResultsGroupBy(
  *   'category',
  *   undefined,
  *   records => Math.max(...records.map(record => record['id'])),
  * )
  *
  *
  * @return {object}          {'group1': ineratee return, 'group2': valueIteratee return}
  */
  getResultsGroupBy(
    field: string,
    labelIteratee?: (groupRecords: dataType, groupField: string) => string
      = (groupRecords, groupField) => groupField,
    valueIteratee?: (groupRecords: dataType, groupField: string) => number
      = groupRecords => groupRecords.length,
  ): Array<{ id: string, title: number }>
  {
    return map(
      groupBy(this.results, x => x[field]),
      (groupRecords, groupField) =>
      ({
        id: labelIteratee(groupRecords, groupField),
        title: valueIteratee(groupRecords, groupField),
      }),
    );
  }

  /**
   * Calculate a PivotTable about the data filtered _results
   * @param  {string} column Observed record field
   * @param  {function} method this method get an array results -> column value
   * @param  {string} [group]  other column which unique values showes each observed field value
   * @return {number|object}
   * @since 3.2.0
   * @example
   * dataModel.getPivotTable('gender', countUnique)
   * // -> 2
   *
   * dataModel.getPivotTable('visits', max, 'gender')
   * // ->[
   *  { id: '1', title: '2017-07-23' },
   *  { id: '2', title: '2017-07-22' },
   *  { id: 'max', title: '2017-07-23' },
   * ]
   *
   * data.getPivotTable(countUnique)
   * // ->
   * { id: 4, name: 4, gender: 2, visits: 4 }
   */
  getPivotTable(propColumn: string|Function, propMethod?: Function, group?: string)
  {
    if (typeof propColumn === 'undefined')
    {
      return null;
    }

    if (typeof propColumn === 'string' && typeof propMethod === 'function')
    {
      const column = propColumn;
      const method = propMethod;

      const collectData = data =>
        data.reduce(
          (result, record) =>
          {
            const value = record[column];

            if (typeof value !== 'undefined')
            {
              result.push(
                isNaN(value) ? value : parseFloat(value),
              );
            }

            return result;
          },
          [],
        );

      const data = collectData(this.results);

      let results = [];

      if (group)
      {
        results = this.getResultsGroupBy(
          group,
          undefined,
          records => method(records.map(record => record[column])),
        );
      }

      results.push({
        id: method.name,
        title: data.length ? method(data) : 0,
      });

      return (results.length === 1) ? results[0].title : results;
    }
    else if (typeof propColumn === 'function')
    {
      const method = propColumn;
      const columns = Object.keys(this.results[0]);

      return columns.reduce(
        (result, column) =>
        {
          const newResult = result;

          newResult[column] = this.getPivotTable(column, method);
          return newResult;
        },
        {},
      );
    }
    return null;
  }
}

export default Data;
<commit_msg>fix(models/data): number sort compare<commit_after>// @flow

import PropTypes from 'prop-types';
import checkPropTypes from 'check-prop-types';
import forIn from 'lodash/forIn';
import isEmpty from 'lodash/isEmpty';
import isEqual from 'lodash/isEqual';
import groupBy from 'lodash/groupBy';
import map from 'lodash/map';

import type { dataType, orderType, filterType, paginateType } from './data.type';

const DIRECTIONS = ['asc', 'desc'];
const FILTER_SCHEMA = {
  id: PropTypes.string.isRequired,
  handler: PropTypes.func.isRequired,
  arguments: PropTypes.arrayOf(PropTypes.string),
  status: PropTypes.bool.isRequired,
};

/**
 * Helper to Data grid
 * Object transforms: paginate, sort, filters
 *
 * @since 1.0.0
 * @class
 * @param  {Object} data     full data Object
 * @param  {Object} [settings]
 * @example
 * // data
 * [
 *   { id: 1, name: 'Megan J. Cushman', gender: 1, visits: '2017-07-23' },
 *   { id: 2, name: 'Taylor R. Fallin', gender: 2, visits: '2017-07-22' },
 *   { id: 3, name: 'Jose C. Rosado', gender: 1, visits: '2017-07-20' },
 *   { id: 4, name: 'Sammy C. Brandt', gender: 1, visits: '2017-07-10' },
 * ];
 *
 * // settings
 *
 * {
 *   paginate:
 *   {
 *     limit: 2,
 *   },
 *   order:
 *   {
 *     column: 'name',
 *     direction: 'desc',
 *   },
 *   filters:
 *   [
 *     {
 *       id: 'search',
 *       handler: (record, value) =>
 *         record.name
 *           .toString()
 *           .toLowerCase()
 *           .indexOf(value.toString().toLowerCase()) >= 0,
 *       arguments: [],
 *       status: false,
 *     },
 *   ],
 * };
 *
 * new Data(data, settings);
 */
class Data
{
  _data: dataType;
  _results: dataType = [];
  _order: orderType;
  _filters: filterType[];
  _paginate: paginateType;

  /**
   * @constructs
   * @private
   * @param  {Object} data     full data Object
   * @param  {Object} [settings]
   */
  constructor(
    data: dataType,
    settings?: {
      order?: orderType,
      filters?: Array<filterType>,
      paginate?: paginateType,
    } = {},
  )
  {
    if (data === undefined || typeof settings !== typeof {})
    {
      return false;
    }

    this._data = data;

    this._results = this._data.concat([]);

    this._order =
    {
      column: '',
      direction: 'asc', // asc || desc
    };

    this._filters = [];

    this._paginate =
    {
      page: 1,
      limit: 20,
      totalPage: 0,
      results: null,
      nextPage: null,
      prevPage: null,
    };

    this.order = settings.order || {};
    this.filters = settings.filters || [];
    this.paginate = settings.paginate || { page: 1 };
  }

  /* !- Getter Setter */

  /**
   * Raw data.
   * @type {array}
   */
  get data(): dataType
  {
    return this._data;
  }

  set data(data: dataType)
  {
    this._data = data;
    this.handle();
  }

  /**
   * Handled data results.
   * @type {array}
   */
  get results(): dataType
  {
    return this._results;
  }

  set results(results: dataType)
  {
    this._results = results;
  }

  /**
   * Current order settings
   * @type {object}
   * @example
   *
   * const data = new Data(json);
   *
   * data.order = {column: 'name', direction: 'asc'};
   * // => data.results
   */
  get order(): orderType
  {
    return this._order;
  }

  set order(settings: orderType)
  {
    if (isEmpty(settings))
    {
      return;
    }

    const order = Object.assign({}, this._order);

    if (!settings.direction && settings.column === order.column)
    {
      order.direction = DIRECTIONS[+!DIRECTIONS.indexOf(order.direction)];
    }
    else if (
      settings.direction !== this.order.direction
      && DIRECTIONS.indexOf(settings.direction) !== -1
    )
    {
      order.direction = settings.direction;
    }

    if (!order.direction)
    {
      order.direction = DIRECTIONS[0];
    }

    if (settings.column !== order.column &&
        Object.keys(this.data[0]).indexOf(settings.column) !== -1)
    {
      order.column = settings.column;
    }

    if (!isEqual(order, this.order))
    {
      this._order = order;
      this.handle();
    }
  }

  /**
   * Filter Collections
   * @type {array}
   * @example
   *
   * const data = new Data(json);
   *
   * data.filters = [
   * {
   *    id: 'foo',
   *    handler: (record, foo, bar) => record.foo == foo,
   *    arguments: ['foo', 'bar'],
   *    status: true,
   * }];
   *
   * // => data.results
   */
  get filters(): filterType[]
  {
    return this._filters;
  }

  set filters(newFilters: filterType[] | filterType)
  {
    const filters = (Array.isArray(newFilters)) ?
      newFilters : [newFilters];

    let modified = false;

    filters.forEach((filter) =>
    {
      const filterIndex = this.filters.findIndex(f => f.id === filter.id);

      if (filterIndex === -1)
      {
        modified = this._registerNewFilter(filter);
      }
      else if (!filter.arguments || isEmpty(filter.arguments))
      {
        if (this._inactiveFilterByIndex(filterIndex))
        {
          modified = true;
        }
      }
      else if (this._updateFilterByIndex(filterIndex, filter))
      {
        modified = true;
      }
    });

    if (modified)
    {
      this.handle();
    }
  }

  /**
   * Current order settings
   * @type {object}
   * @example
   *
   * const data = new Data(json);
   *
   * data.paginate = {
   *    page: 3,
   *    limit: 1
   * };
   *
   * // => data.paginate.results
   */
  get paginate(): paginateType
  {
    return this._paginate;
  }

  set paginate(options: paginateType)
  {
    if (!isEmpty(options))
    {
      this._paginate = this._pagination(options);
    }
  }

  /* !- Private methods */

  /**
   * Pagination
   *
   * @private
   * @return {void}
   */
  _pagination(options: paginateType): paginateType
  {
    if (typeof options !== 'object')
    {
      return this._paginate;
    }

    const paginate = Object.assign({}, this._paginate, options);

    if (typeof options.limit === 'number')
    {
      paginate.limit = options.limit;
    }

    paginate.total = this._results.length;
    paginate.totalPage = Math.ceil(paginate.total / paginate.limit);

    if (typeof options.page === 'number' &&
        options.page > 0 &&
        options.page <= paginate.totalPage)
    {
      paginate.page = Math.ceil(options.page);
    }
    else
    {
      paginate.page = this._paginate.page;
    }

    const offset = (paginate.page - 1) * paginate.limit;

    paginate.results = this._results.slice(offset, offset + paginate.limit);
    paginate.nextPage = (paginate.page < paginate.totalPage)
      ? paginate.page + 1 : null;
    paginate.prevPage = (paginate.page > 1)
      ? paginate.page - 1 : null;

    return paginate;
  }

  /**
   * Sorting _results object by key (alias column) and directions
   * this.order = {column: 'name', direction: 'desc'}
   *
   * if this.order.column is null method not apply.
   *
   * @private
   * @return {void}
   */
  _sort()
  {
    if (this.order.column)
    {
      this._results.sort((recordOne, recordTwo) =>
      {
        const a = recordOne[this.order.column] || '';
        const b = recordTwo[this.order.column] || '';

        // number
        if (!isNaN(a) && !isNaN(b))
        {
          return +(a >= b);
        }

        // string
        return a.toLowerCase().localeCompare(b.toLowerCase());
      });

      if (this.order.direction !== 'asc')
      {
        this._results.reverse();
      }
    }
  }

  /**
   * Apply registered filters
   *
   * @private
   * @return {void}
   */
  _filter()
  {
    this._results = this._results.filter((record) =>
    {
      let proper = true;

      this._filters
        .map((filter) =>
        {
          if (proper && filter.status && !filter.handler(record, ...filter.arguments))
          {
            proper = false;
          }

          return true;
        });

      return proper;
    });
  }

  /**
   * Push new filter object to _filters
   * if it is corresponds to scheme
   *
   * @private
   * @param  {object} filter
   * @return {boolean} successfull of registration
   */
  _registerNewFilter(filter: filterType): boolean
  {
    const result = checkPropTypes(
      FILTER_SCHEMA,
      filter,
      'filter',
      'Data',
    );

    if (result)
    {
      return false;
    }

    this._filters.push(filter);
    return true;
  }

  /**
   * Inactivate filter by index of _filters
   *
   * @private
   * @param  {int} index
   * @return {bool} true
   */
  _inactiveFilterByIndex(index: number): boolean
  {
    if (!this._filters[index])
    {
      return false;
    }

    this._filters[index].status = false;
    return true;
  }

  /**
   * Update current filter property
   *
   * @private
   * @param  {int} index
   * @param  {object} filter new filter properties
   * @return {boolean} successfull of update process
   */
  _updateFilterByIndex(index: number, filter: filterType): boolean
  {
    let modified = false;
    const newFilter = this._filters[index];

    forIn(newFilter, (value, key) =>
    {
      if (typeof filter[key] === typeof value)
      {
        newFilter[key] = filter[key];
        modified = true;
      }
    });

    if (modified)
    {
      this._filters[index] = newFilter;
      return true;
    }

    return false;
  }

  /**
   * Remove current filter (oposite registerFilter)
   *
   * @private
   * @param  {int} index
   * @return {boolean}
   * @todo not used yet
   */
  // _terminateFilterByIndex(index)
  // {
  //   return true;
  // }

  /* !- Public methods */

  /**
  * Data handling process -> this.results
  * - apply filters
  * - sort results
  *
  * @return {void}
  */
  handle(): void
  {
    this._results = this._data.concat([]);

    if (!isEmpty(this.filters))
    {
      this._filter();
    }

    this._sort();

    this._paginate = this._pagination(this._paginate);
  }

  /**
   * Return data element by index
   * @param  {int} index
   * @return {object}
   */
  getDataByIndex(index: number): {} | boolean
  {
    if (typeof this._data[index] === 'undefined')
    {
      return false;
    }

    return this.data[index];
  }

  /**
   * Return results element by index
   * @param  {int} index
   * @return {object}
   */
  getResultByIndex(index: number): {} | boolean
  {
    if (typeof this.results[index] === 'undefined')
    {
      return false;
    }

    return this.results[index];
  }

  /**
  * It returns grouped results
  * @param  {string} field    grouping result by this object key
  * @param  {function} [valueIteratee = (records, field) => records.length]
  * group field items method. Default is count group elements
  *
  * @param  {function} [labelIteratee = (records, field) => field]
  * group field value format method. Default return original value
  *
  * @example
  * <caption>Grouping by date, where the value sum every record person property
  * [{date: '2016-02-03', person: '3'}...]</caption>
  * // return {'2016-02-03': 22, '2016-02-04': 25 }
  * groupByResults('date', (value, key) => (_.sum(_.map(value, i => parseInt(i.person)))))
  *
  * // grouping by category where the value max id each group
  *
  * dataModel.getResultsGroupBy(
  *   'category',
  *   undefined,
  *   records => Math.max(...records.map(record => record['id'])),
  * )
  *
  *
  * @return {object}          {'group1': ineratee return, 'group2': valueIteratee return}
  */
  getResultsGroupBy(
    field: string,
    labelIteratee?: (groupRecords: dataType, groupField: string) => string
      = (groupRecords, groupField) => groupField,
    valueIteratee?: (groupRecords: dataType, groupField: string) => number
      = groupRecords => groupRecords.length,
  ): Array<{ id: string, title: number }>
  {
    return map(
      groupBy(this.results, x => x[field]),
      (groupRecords, groupField) =>
      ({
        id: labelIteratee(groupRecords, groupField),
        title: valueIteratee(groupRecords, groupField),
      }),
    );
  }

  /**
   * Calculate a PivotTable about the data filtered _results
   * @param  {string} column Observed record field
   * @param  {function} method this method get an array results -> column value
   * @param  {string} [group]  other column which unique values showes each observed field value
   * @return {number|object}
   * @since 3.2.0
   * @example
   * dataModel.getPivotTable('gender', countUnique)
   * // -> 2
   *
   * dataModel.getPivotTable('visits', max, 'gender')
   * // ->[
   *  { id: '1', title: '2017-07-23' },
   *  { id: '2', title: '2017-07-22' },
   *  { id: 'max', title: '2017-07-23' },
   * ]
   *
   * data.getPivotTable(countUnique)
   * // ->
   * { id: 4, name: 4, gender: 2, visits: 4 }
   */
  getPivotTable(propColumn: string|Function, propMethod?: Function, group?: string)
  {
    if (typeof propColumn === 'undefined')
    {
      return null;
    }

    if (typeof propColumn === 'string' && typeof propMethod === 'function')
    {
      const column = propColumn;
      const method = propMethod;

      const collectData = data =>
        data.reduce(
          (result, record) =>
          {
            const value = record[column];

            if (typeof value !== 'undefined')
            {
              result.push(
                isNaN(value) ? value : parseFloat(value),
              );
            }

            return result;
          },
          [],
        );

      const data = collectData(this.results);

      let results = [];

      if (group)
      {
        results = this.getResultsGroupBy(
          group,
          undefined,
          records => method(records.map(record => record[column])),
        );
      }

      results.push({
        id: method.name,
        title: data.length ? method(data) : 0,
      });

      return (results.length === 1) ? results[0].title : results;
    }
    else if (typeof propColumn === 'function')
    {
      const method = propColumn;
      const columns = Object.keys(this.results[0]);

      return columns.reduce(
        (result, column) =>
        {
          const newResult = result;

          newResult[column] = this.getPivotTable(column, method);
          return newResult;
        },
        {},
      );
    }
    return null;
  }
}

export default Data;
<|endoftext|>


<commit_before>(function () {
  'use strict';

  var toolsMenuPopup,
    moreToolsMenuPopup,
    prefs = {
      showWhenEmpty: false,
      sortItems: false,
      toolsToKeep: '',
      toolsToMove: ''
    },
    itemsToKeep,
    nativeTools = 'activityManager|addonsManager|addonsmgr|addressBook|'
      + 'applyFilters|applyFiltersToSelection|browserToolsSeparator|'
      + 'cmd_switchprofile|deleteJunk|devToolsSeparator|downloadmgr|filtersCmd|'
      + 'image|javascriptConsole|menu_accountmgr|menu_cookieManager|'
      + 'menu_Filters|menu_imageManager|menu_import|menu_openAddons|'
      + 'menu_openDownloads|menu_openSavedFilesWnd|menu_pageInfo|'
      + 'menu_passwordManager|menu_popupManager|menu_preferences|menu_search|'
      + 'menu_search_addresses|menu_SearchAddresses|menu_SearchMail|'
      + 'menu_searchWeb|menu_translate|menu_validate|navBeginGlobalItems|popup|'
      + 'prefSep|privateBrowsingItem|runJunkControls|sanitizeItem|'
      + 'sanitizeSeparator|sep_switchprofile|sep_validate|sync-setup|'
      + 'sync-syncnowitem|tasksDataman|tasksMenuAddressBook|'
      + 'tasksMenuAfterAddressesSeparator|tasksMenuAfterApplySeparator|'
      + 'tasksMenuAfterDeleteSeparator|tasksMenuMail|webDeveloperMenu';

  String.prototype.trimPipes = function () {
    return this.replace(/\|{2,}/g, '|').replace(/^\||\|$/g, '');
  };

  function updateItemsToKeep() {
    var pattern;

    // Keep the native items.
    pattern = nativeTools;

    // Move the native items selected by the user.
    if (prefs.toolsToMove) {
      pattern = pattern.replace(new RegExp(
        '\\b' + prefs.toolsToMove.replace(/\|/g, '\\b|\\b') + '\\b',
        'g'
      ), '').trimPipes();
    }

    // Keep the extension items selected by the user.
    if (prefs.toolsToKeep) {
      pattern += '|' + prefs.toolsToKeep;
    }

    // Keep the items that doesn't have an id.
    pattern += '|';

    itemsToKeep = new RegExp('^(' + pattern + ')$');
  }

  function getMenu(node, type) {
    var doc, xpath;

    function nsResolver() {
      return 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
    }

    doc = node.ownerDocument || node;
    if (type === 'tools') {
      xpath = '//xul:menupopup[@id="menu_ToolsPopup" or @id="taskPopup"]/*';
    } else if (type === 'moreTools') {
      xpath = '//xul:menupopup[@id="more-tools-menupopup"]/*';
    }
    return doc.evaluate(
      xpath,
      doc,
      nsResolver,
      XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
      null
    );
  }

  function isMoreToolsEmpty() {
    //var menu, a, b, item, allItemsHidden;

    if (moreToolsMenuPopup.childNodes.length === 2) {
      return true;
    }

    //allItemsHidden = true;
    //menu = getMenu(document, 'moreTools');
    //for (a = 0, b = menu.snapshotLength - 1; a <= b; a += 1) {
    //  item = menu.snapshotItem(a);
    //  if (item) {
    //    if (!(item.hasAttribute('hidden') && item.getAttribute('hidden'))) {
    //      allItemsHidden = false;
    //    }
    //  }
    //}
    //if (allItemsHidden) {
    //  return true;
    //}

    return false;
  }

  function toggleMoreTools() {
    if (isMoreToolsEmpty()) {
      document.getElementById('more-tools-label').setAttribute('hidden', false);
      document.getElementById('more-tools-sep').setAttribute('hidden', false);
    } else {
      document.getElementById('more-tools-label').setAttribute('hidden', true);
      document.getElementById('more-tools-sep').setAttribute('hidden', true);
    }

    if (isMoreToolsEmpty() && !prefs.showWhenEmpty) {
      document.getElementById('more-tools-menu').setAttribute('hidden', true);
    } else {
      document.getElementById('more-tools-menu').setAttribute('hidden', false);
    }
  }

  function sortMoreTools() {
    var sortService;

    sortService = Components.classes['@mozilla.org/xul/xul-sort-service;1']
      .getService(Components.interfaces.nsIXULSortService);
    if (prefs.sortItems && !isMoreToolsEmpty()) {
      sortService.sort(moreToolsMenuPopup, 'label', 'ascending');
    } else {
      sortService.sort(moreToolsMenuPopup, 'defaultOrder', 'integer');
    }
  }

  function toggleSeparators(menu) {
    var a, b, item, prevItem, nextItem;

    for (a = 0, b = menu.snapshotLength - 1; a <= b; a += 1) {
      item = menu.snapshotItem(a);
      if (item.tagName === 'menuseparator' && item.id) {
        prevItem = item.previousSibling;
        nextItem = item.nextSibling;
        if (prevItem && prevItem.tagName !== 'menuseparator' &&
            nextItem && nextItem.tagName !== 'menuseparator') {
          document.getElementById(item.id).setAttribute('hidden', false);
        } else {
          document.getElementById(item.id).setAttribute('hidden', true);
        }
      }
    }
  }

  function moveTools() {
    var a, b, menu, item, nextItem;

    // Move items to More Tools menu.
    menu = getMenu(document, 'tools');
    for (a = 0, b = menu.snapshotLength - 1; a <= b; a += 1) {
      item = menu.snapshotItem(a);
      if (!item.hasAttribute('defaultOrder')) {
        item.setAttribute('defaultOrder', a);
      }
      if (!itemsToKeep.test(item.id)) {
        nextItem = item.nextSibling;
        if (nextItem) {
          item.setAttribute('nextItemID', nextItem.id);
        }
        moreToolsMenuPopup.appendChild(item);
      }
    }

    // Move items back to Tools menu.
    menu = getMenu(document, 'moreTools');
    for (a = menu.snapshotLength - 1, b = 0; a >= b; a -= 1) {
      item = menu.snapshotItem(a);
      if (itemsToKeep.test(item.id)) {
        if (item.hasAttribute('nextItemID')) {
          nextItem = document.getElementById(item.getAttribute('nextItemID'));
        } else {
          nextItem = null;
        }
        if (nextItem) {
          toolsMenuPopup.insertBefore(item, nextItem);
        } else {
          toolsMenuPopup.appendChild(item);
        }
      }
    }
    toggleSeparators(getMenu(document, 'tools'));

    // Finish More Tools menu.
    sortMoreTools();
    toggleSeparators(getMenu(document, 'moreTools'));
    toggleMoreTools();
  }

  function dumpTools() {
    var i, l, el, menu, message1, message2;

    message1 = 'Items found in Tools menu at startup:\n';
    message2 = '\n\ntoolsToMove: ';
    menu = getMenu(document, 'tools');
    for (i = 0, l = menu.snapshotLength; i < l; i += 1) {
      message1 += '\t' + menu.snapshotItem(i).id
        + '\t' + menu.snapshotItem(i).label + '\n';
      message2 += menu.snapshotItem(i).id + ', ';
    }
    Components.classes['@mozilla.org/consoleservice;1']
      .getService(Components.interfaces.nsIConsoleService)
      .logStringMessage(message1 + message2);
  }

  function makePattern(s) {
    return s.replace(/(\-)/g, '\\$1').replace(/\s*[,|]\s*/g, '|').trimPipes();
  }

  function loadPrefs() {
    var prefBranch;

    prefBranch = Components.classes['@mozilla.org/preferences-service;1']
      .getService(Components.interfaces.nsIPrefService)
      .getBranch('extensions.moretools.');
    prefs.showWhenEmpty = prefBranch.getBoolPref('showWhenEmpty');
    prefs.sortItems = prefBranch.getBoolPref('sortItems');
    prefs.toolsToMove = makePattern(prefBranch.getCharPref('toolsToMove'));
    prefs.toolsToKeep = makePattern(prefBranch.getCharPref('toolsToKeep'));
  }

  function observePrefs() {
    var prefBranch;

    prefBranch = Components.classes['@mozilla.org/preferences-service;1']
      .getService(Components.interfaces.nsIPrefBranch);
    prefBranch.QueryInterface(Components.interfaces.nsIPrefBranch2);
    prefBranch.addObserver('extensions.moretools.', {
      observe: function (aSubject, aTopic, aData) {
        if (aTopic === 'nsPref:changed') {
          loadPrefs();
          switch (aData) {
          case 'extensions.moretools.showWhenEmpty':
            toggleMoreTools();
            break;
          case 'extensions.moretools.sortItems':
            sortMoreTools();
            toggleSeparators(getMenu(document, 'moreTools'));
            toggleMoreTools();
            break;
          case 'extensions.moretools.toolsToMove':
          case 'extensions.moretools.toolsToKeep':
            updateItemsToKeep();
            moveTools();
            break;
          }
        }
      }
    }, false);
    prefBranch.QueryInterface(Components.interfaces.nsIPrefBranch);
  }

  window.addEventListener('load', function () {
    var i, l, el;

    // This line must be commented out to make the extension work properly.
    // return dumpTools();

    // Look up nodes now that getElementById() is safe.
    toolsMenuPopup =
      document.getElementById('menu_ToolsPopup') || // Firefox
      document.getElementById('taskPopup');         // Thunderbird, SeaMonkey
    moreToolsMenuPopup = document.getElementById('more-tools-menupopup');

    // Load user-configured options and monitor changes made to them.
    loadPrefs();
    observePrefs();

    // Update the list of items to be kept in Tools menu.
    updateItemsToKeep();

    // Move any tools not in the list of items to keep.
    moveTools();

    // Move tools that are added later.
    toolsMenuPopup.addEventListener('DOMNodeInserted', moveTools, true);
  }, true);
}());
<commit_msg>Just code formatting.<commit_after>(function () {
  'use strict';

  var toolsMenuPopup,
    moreToolsMenuPopup,
    prefs = {
      showWhenEmpty: false,
      sortItems: false,
      toolsToKeep: '',
      toolsToMove: ''
    },
    itemsToKeep,
    nativeTools = 'activityManager|addonsManager|addonsmgr|addressBook|'
      + 'applyFilters|applyFiltersToSelection|browserToolsSeparator|'
      + 'cmd_switchprofile|deleteJunk|devToolsSeparator|downloadmgr|filtersCmd|'
      + 'image|javascriptConsole|menu_accountmgr|menu_cookieManager|'
      + 'menu_Filters|menu_imageManager|menu_import|menu_openAddons|'
      + 'menu_openDownloads|menu_openSavedFilesWnd|menu_pageInfo|'
      + 'menu_passwordManager|menu_popupManager|menu_preferences|menu_search|'
      + 'menu_search_addresses|menu_SearchAddresses|menu_SearchMail|'
      + 'menu_searchWeb|menu_translate|menu_validate|navBeginGlobalItems|popup|'
      + 'prefSep|privateBrowsingItem|runJunkControls|sanitizeItem|'
      + 'sanitizeSeparator|sep_switchprofile|sep_validate|sync-setup|'
      + 'sync-syncnowitem|tasksDataman|tasksMenuAddressBook|'
      + 'tasksMenuAfterAddressesSeparator|tasksMenuAfterApplySeparator|'
      + 'tasksMenuAfterDeleteSeparator|tasksMenuMail|webDeveloperMenu';

  String.prototype.trimPipes = function () {
    return this.replace(/\|{2,}/g, '|').replace(/^\||\|$/g, '');
  };

  function updateItemsToKeep() {
    var pattern;

    // Keep the native items.
    pattern = nativeTools;

    // Move the native items selected by the user.
    if (prefs.toolsToMove) {
      pattern = pattern.replace(new RegExp(
        '\\b' + prefs.toolsToMove.replace(/\|/g, '\\b|\\b') + '\\b',
        'g'
      ), '').trimPipes();
    }

    // Keep the extension items selected by the user.
    if (prefs.toolsToKeep) {
      pattern += '|' + prefs.toolsToKeep;
    }

    // Keep the items that doesn't have an id.
    pattern += '|';

    itemsToKeep = new RegExp('^(' + pattern + ')$');
  }

  function getMenu(node, type) {
    var doc, xpath;

    function nsResolver() {
      return 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
    }

    doc = node.ownerDocument || node;
    if (type === 'tools') {
      xpath = '//xul:menupopup[@id="menu_ToolsPopup" or @id="taskPopup"]/*';
    } else if (type === 'moreTools') {
      xpath = '//xul:menupopup[@id="more-tools-menupopup"]/*';
    }
    return doc.evaluate(
      xpath,
      doc,
      nsResolver,
      XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
      null
    );
  }

  function isMoreToolsEmpty() {
    //var menu, a, b, item, allItemsHidden;

    if (moreToolsMenuPopup.childNodes.length === 2) {
      return true;
    }

    //allItemsHidden = true;
    //menu = getMenu(document, 'moreTools');
    //for (a = 0, b = menu.snapshotLength - 1; a <= b; a += 1) {
    //  item = menu.snapshotItem(a);
    //  if (item) {
    //    if (!(item.hasAttribute('hidden') && item.getAttribute('hidden'))) {
    //      allItemsHidden = false;
    //    }
    //  }
    //}
    //if (allItemsHidden) {
    //  return true;
    //}

    return false;
  }

  function toggleMoreTools() {
    if (isMoreToolsEmpty()) {
      document.getElementById('more-tools-label').setAttribute('hidden', false);
      document.getElementById('more-tools-sep').setAttribute('hidden', false);
    } else {
      document.getElementById('more-tools-label').setAttribute('hidden', true);
      document.getElementById('more-tools-sep').setAttribute('hidden', true);
    }

    if (isMoreToolsEmpty() && !prefs.showWhenEmpty) {
      document.getElementById('more-tools-menu').setAttribute('hidden', true);
    } else {
      document.getElementById('more-tools-menu').setAttribute('hidden', false);
    }
  }

  function sortMoreTools() {
    var sortService;

    sortService = Components.classes['@mozilla.org/xul/xul-sort-service;1']
      .getService(Components.interfaces.nsIXULSortService);
    if (prefs.sortItems && !isMoreToolsEmpty()) {
      sortService.sort(moreToolsMenuPopup, 'label', 'ascending');
    } else {
      sortService.sort(moreToolsMenuPopup, 'defaultOrder', 'integer');
    }
  }

  function toggleSeparators(menu) {
    var a, b, item, prevItem, nextItem;

    for (a = 0, b = menu.snapshotLength - 1; a <= b; a += 1) {
      item = menu.snapshotItem(a);
      if (item.tagName === 'menuseparator' && item.id) {
        prevItem = item.previousSibling;
        nextItem = item.nextSibling;
        if (prevItem && prevItem.tagName !== 'menuseparator' &&
            nextItem && nextItem.tagName !== 'menuseparator') {
          document.getElementById(item.id).setAttribute('hidden', false);
        } else {
          document.getElementById(item.id).setAttribute('hidden', true);
        }
      }
    }
  }

  function moveTools() {
    var a, b, menu, item, nextItem;

    // Move items to More Tools menu.
    menu = getMenu(document, 'tools');
    for (a = 0, b = menu.snapshotLength - 1; a <= b; a += 1) {
      item = menu.snapshotItem(a);
      if (!item.hasAttribute('defaultOrder')) {
        item.setAttribute('defaultOrder', a);
      }
      if (!itemsToKeep.test(item.id)) {
        nextItem = item.nextSibling;
        if (nextItem) {
          item.setAttribute('nextItemID', nextItem.id);
        }
        moreToolsMenuPopup.appendChild(item);
      }
    }

    // Move items back to Tools menu.
    menu = getMenu(document, 'moreTools');
    for (a = menu.snapshotLength - 1, b = 0; a >= b; a -= 1) {
      item = menu.snapshotItem(a);
      if (itemsToKeep.test(item.id)) {
        if (item.hasAttribute('nextItemID')) {
          nextItem = document.getElementById(item.getAttribute('nextItemID'));
        } else {
          nextItem = null;
        }
        if (nextItem) {
          toolsMenuPopup.insertBefore(item, nextItem);
        } else {
          toolsMenuPopup.appendChild(item);
        }
      }
    }
    toggleSeparators(getMenu(document, 'tools'));

    // Finish More Tools menu.
    sortMoreTools();
    toggleSeparators(getMenu(document, 'moreTools'));
    toggleMoreTools();
  }

  function dumpTools() {
    var i, l, menu, message1, message2;

    message1 = 'Items found in Tools menu at startup:\n';
    message2 = '\n\ntoolsToMove: ';
    menu = getMenu(document, 'tools');
    for (i = 0, l = menu.snapshotLength; i < l; i += 1) {
      message1 += '\t' + menu.snapshotItem(i).id
        + '\t' + menu.snapshotItem(i).label + '\n';
      message2 += menu.snapshotItem(i).id + ', ';
    }
    Components.classes['@mozilla.org/consoleservice;1']
      .getService(Components.interfaces.nsIConsoleService)
      .logStringMessage(message1 + message2);
  }

  function makePattern(s) {
    return s.replace(/(\-)/g, '\\$1').replace(/\s*[,|]\s*/g, '|').trimPipes();
  }

  function loadPrefs() {
    var prefBranch;

    prefBranch = Components.classes['@mozilla.org/preferences-service;1']
      .getService(Components.interfaces.nsIPrefService)
      .getBranch('extensions.moretools.');
    prefs.showWhenEmpty = prefBranch.getBoolPref('showWhenEmpty');
    prefs.sortItems = prefBranch.getBoolPref('sortItems');
    prefs.toolsToMove = makePattern(prefBranch.getCharPref('toolsToMove'));
    prefs.toolsToKeep = makePattern(prefBranch.getCharPref('toolsToKeep'));
  }

  function observePrefs() {
    var prefBranch;

    prefBranch = Components.classes['@mozilla.org/preferences-service;1']
      .getService(Components.interfaces.nsIPrefBranch);
    prefBranch.QueryInterface(Components.interfaces.nsIPrefBranch2);
    prefBranch.addObserver('extensions.moretools.', {
      observe: function (aSubject, aTopic, aData) {
        if (aTopic === 'nsPref:changed') {
          loadPrefs();
          switch (aData) {
          case 'extensions.moretools.showWhenEmpty':
            toggleMoreTools();
            break;
          case 'extensions.moretools.sortItems':
            sortMoreTools();
            toggleSeparators(getMenu(document, 'moreTools'));
            toggleMoreTools();
            break;
          case 'extensions.moretools.toolsToMove':
          case 'extensions.moretools.toolsToKeep':
            updateItemsToKeep();
            moveTools();
            break;
          }
        }
      }
    }, false);
    prefBranch.QueryInterface(Components.interfaces.nsIPrefBranch);
  }

  window.addEventListener('load', function () {
    // This line must be commented out to make the extension work properly.
    // return dumpTools();

    // Look up nodes now that getElementById() is safe.
    toolsMenuPopup =
      document.getElementById('menu_ToolsPopup') || // Firefox
      document.getElementById('taskPopup');         // Thunderbird, SeaMonkey
    moreToolsMenuPopup = document.getElementById('more-tools-menupopup');

    // Load user-configured options and monitor changes made to them.
    loadPrefs();
    observePrefs();

    // Update the list of items to be kept in Tools menu.
    updateItemsToKeep();

    // Move any tools not in the list of items to keep.
    moveTools();

    // Move tools that are added later.
    toolsMenuPopup.addEventListener('DOMNodeInserted', moveTools, true);
  }, true);
}());
<|endoftext|>


<commit_before>/* eslint func-names: 0 */
/* eslint no-invalid-this: 0 */

const mongoose = require('mongoose');

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

const messageSchema = new mongoose.Schema({
    'contents': {
        'required': true,
        'type': String
    },
    'createdAt': {
        'default': Date.now,
        'type': Date
    },
    'createdBy': {
        'ref': 'profile',
        'type': mongoose.Schema.Types.ObjectId
    },
    'editedBy': {
        'ref': 'profile',
        'type': mongoose.Schema.Types.ObjectId
    },
    'updatedAt': {
        'default': Date.now,
        'type': Date
    }
});

const postSchema = new mongoose.Schema({
    'contents': String,
    'createdAt': {
        'default': Date.now,
        'type': Date
    },
    'createdBy': {
        'ref': 'profile',
        'type': mongoose.Schema.Types.ObjectId
    },
    'editedBy': {
        'ref': 'profile',
        'type': mongoose.Schema.Types.ObjectId
    },
    'messageCount': {
        'default': 0,
        'type': Number
    },
    'messages': [messageSchema],
    'pinned': {
        'default': false,
        'type': Boolean
    },
    'slug': String,
    'title': {
        'required': true,
        'type': String
    },
    'updatedAt': {
        'default': Date.now,
        'type': Date
    },
    'views': {
        'default': 0,
        'type': Number
    }
});

postSchema.statics.createPost = function (data, profileId) {

    return new Promise((resolve, reject) => {

        const post = new this({
            'contents': data.contents,
            'createdBy': profileId,
            'slug': data.title.toLowerCase()
                .replace(/[^a-z]+/g, '-')
                .replace(/^\-|\-$/g, ''),
            'title': data.title
        });

        post.save((err, results) => {

            if (err || !results) {

                logger.err('Error creating new post.', err, data);

                reject({
                    'message': 'Internal Server Error',
                    'status': 500
                });

            } else {

                resolve(this.showPostById(results._id));

            }

        });

    });

};

postSchema.statics.updatePostById = function (postId, data, profileId) {

    return new Promise((resolve, reject) => {

        this.findOne({
            '_id': postId,
            'createdBy': profileId
        })
            .exec((execError, post) => {

                if (execError || !post) {

                    logger.err('Error updating existing post.', execError, data);

                    reject({
                        'message': 'Internal Server Error',
                        'status': 500
                    });

                } else {

                    if (typeof data.title !== 'undefined') {

                        post.title = data.title;

                    }

                    if (typeof data.contents !== 'undefined') {

                        post.contents = data.contents;

                    }

                    post.editedBy = profileId;

                    post.save((saveError, results) => {

                        if (saveError || !results) {

                            logger.err('Error updating existing post.', saveError, data);

                            reject({
                                'message': 'Internal Server Error',
                                'status': 500
                            });

                        } else {

                            resolve(this.showPostById(postId));

                        }

                    });

                }

            });

    });

};

postSchema.statics.addMessageToPostById = function (postId, data, profileId) {

    return new Promise((resolve, reject) => {

        this.findOneAndUpdate(
            {
                '_id': postId
            },
            {
                '$push': {
                    'messages': {
                        'contents': data.contents,
                        'createdBy': profileId
                    }
                },
                '$set': {
                    'updatedAt': Date.now()
                }
            },
            {
                'new': true
            },
            (updateError, results) => {

                if (updateError || !results) {

                    logger.err('Error updating existing message.', updateError, data);

                    reject({
                        'message': 'Internal Server Error',
                        'status': 500
                    });

                } else {

                    resolve(this.showMessageById(postId, results.messages.slice(-1)[0]._id));

                }

            }
        );

    });

};

postSchema.statics.updateMessageToPostById = function (postId, messageId, data, profileId) {

    return new Promise((resolve, reject) => {

        this.findOneAndUpdate(
            {
                '_id': postId,
                'messages': {
                    '$elemMatch': {
                        '_id': messageId,
                        'createdBy': profileId
                    }
                }
            },
            {
                '$set': {
                    'messages.$.contents': data.contents,
                    'messages.$.editedBy': profileId,
                    'messages.$.updatedAt': Date.now(),
                    'updatedAt': Date.now()
                }
            },
            {
                'new': true
            },
            (updateError, results) => {

                if (updateError || !results) {

                    logger.err('Error updating existing message.', updateError, data);

                    reject({
                        'message': 'Internal Server Error',
                        'status': 500
                    });

                } else {

                    resolve(this.showMessageById(postId, messageId));

                }

            }
        );

    });

};

postSchema.statics.deletePostById = function (postId, profileId) {

    return new Promise((resolve, reject) => {

        this.findOneAndRemove(
            {
                '_id': postId,
                'createdBy': profileId
            },
            (removeError, post) => {

                if (removeError || !post) {

                    logger.err('Error deleting existing post.', removeError);

                    reject({
                        'message': 'Internal Server Error',
                        'status': 500
                    });

                } else {

                    resolve([]);

                }

            }
        );

    });

};

postSchema.statics.deleteMessageFromPostById = function (postId, messageId, profileId) {

    return new Promise((resolve, reject) => {

        this.findOneAndUpdate(
            {
                '_id': postId,
                'messages': {
                    '$elemMatch': {
                        '_id': messageId,
                        'createdBy': profileId
                    }
                }
            },
            {
                '$pull': {
                    'messages': {
                        '$elemMatch': {
                            '_id': messageId,
                            'createdBy': profileId
                        }
                    }
                },
                '$set': {
                    'updatedAt': Date.now()
                }
            },
            {
                'new': true,
                'upsert': true
            },
            (updateError, results) => {

                if (updateError || !results) {

                    logger.err('Error deleting existing message.', updateError);

                    reject({
                        'message': 'Internal Server Error',
                        'status': 500
                    });

                } else {

                    resolve({});

                }

            }
        );

    });

};

postSchema.statics.showPostById = function (postId) {

    return new Promise((resolve, reject) => {

        this.findByIdAndUpdate(postId, {
            '$inc': {
                'views': 1
            }
        })
            .populate('createdBy')
            .populate('editedBy')
            .populate('messages.createdBy')
            .populate('messages.editedBy')
            .exec((updateError, results) => {

                if (updateError || !results) {

                    logger.err(`Post id ${postId} not found.`);

                    reject({
                        'message': 'Post not found.',
                        'status': 404
                    });

                } else {

                    resolve(results);

                }

            });

    });

};

postSchema.statics.showMessageById = function (postId, messageId) {

    return new Promise((resolve, reject) => {

        this.find({
            'messages._id': messageId
        }, {
            'messages.$': true
        })
            .populate('messages.createdBy')
            .populate('messages.editedBy')
            .exec((findError, results) => {

                if (findError || !results || !results[0] || !results[0].messages) {

                    logger.err(`Message id ${messageId} not found.`);

                    reject({
                        'message': 'Message not found.',
                        'status': 404
                    });

                } else {

                    resolve(results[0].messages[0]);

                }

            });

    });

};

postSchema.statics.listPosts = function () {

    return new Promise((resolve, reject) => {

        this.find()
            .populate('createdBy')
            .populate('editedBy')
            .sort({
                'pinned': -1,
                'updatedAt': -1
            })
            .select('createdAt updatedAt views title slug contents messageCount createdBy editedBy')
            .exec((findError, results) => {

                if (findError || !results) {

                    logger.err('Error retrieving post list.', findError);

                    reject({
                        'message': 'Internal Server Error',
                        'status': 500
                    });

                } else {

                    resolve(results);

                }

            });

    });

};

postSchema.statics.listMessagesByPostId = function (postId) {

    return new Promise((resolve, reject) => {

        this.findByIdAndUpdate(postId, {
            '$inc': {
                'views': 1
            }
        })
            .populate('createdBy')
            .populate('editedBy')
            .populate('messages.createdBy')
            .populate('messages.editedBy')
            .exec((updateError, results) => {

                if (updateError || !results) {

                    logger.err(`Post id ${postId} not found.`);

                    reject({
                        'message': 'Post not found.',
                        'status': 404
                    });

                } else {

                    resolve(results.messages);

                }

            });

    });

};

postSchema.statics.searchPosts = function (query) {

    return new Promise((resolve, reject) => {

        if (query) {

            let modifiedQuery = query;

            modifiedQuery = modifiedQuery.trim().toLowerCase();
            modifiedQuery = modifiedQuery.replace(/[^0-9a-z]+/, '|').replace(/^\||\|$/, '');

            if (modifiedQuery) {

                this.find({
                    'title': {
                        '$regex': new RegExp(query, 'i')
                    }
                })
                    .populate('createdBy')
                    .populate('editedBy')
                    .sort({
                        'updatedAt': -1
                    })
                    .select('createdAt updatedAt views title slug contents messageCount createdBy editedBy')
                    .exec((findError, results) => {

                        if (findError || !results) {

                            logger.err('Error retrieving search results.', findError, query);

                            reject({
                                'message': 'Internal Server Error',
                                'status': 500
                            });

                        } else {

                            resolve(results);

                        }

                    });

            } else {

                resolve([]);

            }

        } else {

            resolve([]);

        }

    });

};

messageSchema.virtual('id').get(function () {

    return this._id.toHexString();

});

messageSchema.set('toJSON', {
    'virtuals': true
});

postSchema.post('findOneAndUpdate', (doc, next) => {

    if (doc && doc.messageCount !== doc.messages.length) {

        doc.messageCount = doc.messages.length;

        doc.save(next);

    }

    next();

});

postSchema.virtual('id').get(function () {

    return this._id.toHexString();

});

postSchema.set('toJSON', {
    'virtuals': true
});

module.exports = mongoose.model('post', postSchema);
<commit_msg>Updated pull syntax.<commit_after>/* eslint func-names: 0 */
/* eslint no-invalid-this: 0 */

const mongoose = require('mongoose');

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

const messageSchema = new mongoose.Schema({
    'contents': {
        'required': true,
        'type': String
    },
    'createdAt': {
        'default': Date.now,
        'type': Date
    },
    'createdBy': {
        'ref': 'profile',
        'type': mongoose.Schema.Types.ObjectId
    },
    'editedBy': {
        'ref': 'profile',
        'type': mongoose.Schema.Types.ObjectId
    },
    'updatedAt': {
        'default': Date.now,
        'type': Date
    }
});

const postSchema = new mongoose.Schema({
    'contents': String,
    'createdAt': {
        'default': Date.now,
        'type': Date
    },
    'createdBy': {
        'ref': 'profile',
        'type': mongoose.Schema.Types.ObjectId
    },
    'editedBy': {
        'ref': 'profile',
        'type': mongoose.Schema.Types.ObjectId
    },
    'messageCount': {
        'default': 0,
        'type': Number
    },
    'messages': [messageSchema],
    'pinned': {
        'default': false,
        'type': Boolean
    },
    'slug': String,
    'title': {
        'required': true,
        'type': String
    },
    'updatedAt': {
        'default': Date.now,
        'type': Date
    },
    'views': {
        'default': 0,
        'type': Number
    }
});

postSchema.statics.createPost = function (data, profileId) {

    return new Promise((resolve, reject) => {

        const post = new this({
            'contents': data.contents,
            'createdBy': profileId,
            'slug': data.title.toLowerCase()
                .replace(/[^a-z]+/g, '-')
                .replace(/^\-|\-$/g, ''),
            'title': data.title
        });

        post.save((err, results) => {

            if (err || !results) {

                logger.err('Error creating new post.', err, data);

                reject({
                    'message': 'Internal Server Error',
                    'status': 500
                });

            } else {

                resolve(this.showPostById(results._id));

            }

        });

    });

};

postSchema.statics.updatePostById = function (postId, data, profileId) {

    return new Promise((resolve, reject) => {

        this.findOne({
            '_id': postId,
            'createdBy': profileId
        })
            .exec((execError, post) => {

                if (execError || !post) {

                    logger.err('Error updating existing post.', execError, data);

                    reject({
                        'message': 'Internal Server Error',
                        'status': 500
                    });

                } else {

                    if (typeof data.title !== 'undefined') {

                        post.title = data.title;

                    }

                    if (typeof data.contents !== 'undefined') {

                        post.contents = data.contents;

                    }

                    post.editedBy = profileId;

                    post.save((saveError, results) => {

                        if (saveError || !results) {

                            logger.err('Error updating existing post.', saveError, data);

                            reject({
                                'message': 'Internal Server Error',
                                'status': 500
                            });

                        } else {

                            resolve(this.showPostById(postId));

                        }

                    });

                }

            });

    });

};

postSchema.statics.addMessageToPostById = function (postId, data, profileId) {

    return new Promise((resolve, reject) => {

        this.findOneAndUpdate(
            {
                '_id': postId
            },
            {
                '$push': {
                    'messages': {
                        'contents': data.contents,
                        'createdBy': profileId
                    }
                },
                '$set': {
                    'updatedAt': Date.now()
                }
            },
            {
                'new': true
            },
            (updateError, results) => {

                if (updateError || !results) {

                    logger.err('Error updating existing message.', updateError, data);

                    reject({
                        'message': 'Internal Server Error',
                        'status': 500
                    });

                } else {

                    resolve(this.showMessageById(postId, results.messages.slice(-1)[0]._id));

                }

            }
        );

    });

};

postSchema.statics.updateMessageToPostById = function (postId, messageId, data, profileId) {

    return new Promise((resolve, reject) => {

        this.findOneAndUpdate(
            {
                '_id': postId,
                'messages': {
                    '$elemMatch': {
                        '_id': messageId,
                        'createdBy': profileId
                    }
                }
            },
            {
                '$set': {
                    'messages.$.contents': data.contents,
                    'messages.$.editedBy': profileId,
                    'messages.$.updatedAt': Date.now(),
                    'updatedAt': Date.now()
                }
            },
            {
                'new': true
            },
            (updateError, results) => {

                if (updateError || !results) {

                    logger.err('Error updating existing message.', updateError, data);

                    reject({
                        'message': 'Internal Server Error',
                        'status': 500
                    });

                } else {

                    resolve(this.showMessageById(postId, messageId));

                }

            }
        );

    });

};

postSchema.statics.deletePostById = function (postId, profileId) {

    return new Promise((resolve, reject) => {

        this.findOneAndRemove(
            {
                '_id': postId,
                'createdBy': profileId
            },
            (removeError, post) => {

                if (removeError || !post) {

                    logger.err('Error deleting existing post.', removeError);

                    reject({
                        'message': 'Internal Server Error',
                        'status': 500
                    });

                } else {

                    resolve([]);

                }

            }
        );

    });

};

postSchema.statics.deleteMessageFromPostById = function (postId, messageId, profileId) {

    return new Promise((resolve, reject) => {

        this.findOneAndUpdate(
            {
                '_id': postId,
                'messages': {
                    '$elemMatch': {
                        '_id': messageId,
                        'createdBy': profileId
                    }
                }
            },
            {
                '$pull': {
                    'messages': {
                        '_id': messageId,
                        'createdBy': profileId
                    }
                },
                '$set': {
                    'updatedAt': Date.now()
                }
            },
            {
                'new': true,
                'upsert': true
            },
            (updateError, results) => {

                if (updateError || !results) {

                    logger.err('Error deleting existing message.', updateError);

                    reject({
                        'message': 'Internal Server Error',
                        'status': 500
                    });

                } else {

                    resolve({});

                }

            }
        );

    });

};

postSchema.statics.showPostById = function (postId) {

    return new Promise((resolve, reject) => {

        this.findByIdAndUpdate(postId, {
            '$inc': {
                'views': 1
            }
        })
            .populate('createdBy')
            .populate('editedBy')
            .populate('messages.createdBy')
            .populate('messages.editedBy')
            .exec((updateError, results) => {

                if (updateError || !results) {

                    logger.err(`Post id ${postId} not found.`);

                    reject({
                        'message': 'Post not found.',
                        'status': 404
                    });

                } else {

                    resolve(results);

                }

            });

    });

};

postSchema.statics.showMessageById = function (postId, messageId) {

    return new Promise((resolve, reject) => {

        this.find({
            'messages._id': messageId
        }, {
            'messages.$': true
        })
            .populate('messages.createdBy')
            .populate('messages.editedBy')
            .exec((findError, results) => {

                if (findError || !results || !results[0] || !results[0].messages) {

                    logger.err(`Message id ${messageId} not found.`);

                    reject({
                        'message': 'Message not found.',
                        'status': 404
                    });

                } else {

                    resolve(results[0].messages[0]);

                }

            });

    });

};

postSchema.statics.listPosts = function () {

    return new Promise((resolve, reject) => {

        this.find()
            .populate('createdBy')
            .populate('editedBy')
            .sort({
                'pinned': -1,
                'updatedAt': -1
            })
            .select('createdAt updatedAt views title slug contents messageCount createdBy editedBy')
            .exec((findError, results) => {

                if (findError || !results) {

                    logger.err('Error retrieving post list.', findError);

                    reject({
                        'message': 'Internal Server Error',
                        'status': 500
                    });

                } else {

                    resolve(results);

                }

            });

    });

};

postSchema.statics.listMessagesByPostId = function (postId) {

    return new Promise((resolve, reject) => {

        this.findByIdAndUpdate(postId, {
            '$inc': {
                'views': 1
            }
        })
            .populate('createdBy')
            .populate('editedBy')
            .populate('messages.createdBy')
            .populate('messages.editedBy')
            .exec((updateError, results) => {

                if (updateError || !results) {

                    logger.err(`Post id ${postId} not found.`);

                    reject({
                        'message': 'Post not found.',
                        'status': 404
                    });

                } else {

                    resolve(results.messages);

                }

            });

    });

};

postSchema.statics.searchPosts = function (query) {

    return new Promise((resolve, reject) => {

        if (query) {

            let modifiedQuery = query;

            modifiedQuery = modifiedQuery.trim().toLowerCase();
            modifiedQuery = modifiedQuery.replace(/[^0-9a-z]+/, '|').replace(/^\||\|$/, '');

            if (modifiedQuery) {

                this.find({
                    'title': {
                        '$regex': new RegExp(query, 'i')
                    }
                })
                    .populate('createdBy')
                    .populate('editedBy')
                    .sort({
                        'updatedAt': -1
                    })
                    .select('createdAt updatedAt views title slug contents messageCount createdBy editedBy')
                    .exec((findError, results) => {

                        if (findError || !results) {

                            logger.err('Error retrieving search results.', findError, query);

                            reject({
                                'message': 'Internal Server Error',
                                'status': 500
                            });

                        } else {

                            resolve(results);

                        }

                    });

            } else {

                resolve([]);

            }

        } else {

            resolve([]);

        }

    });

};

messageSchema.virtual('id').get(function () {

    return this._id.toHexString();

});

messageSchema.set('toJSON', {
    'virtuals': true
});

postSchema.post('findOneAndUpdate', (doc, next) => {

    if (doc && doc.messageCount !== doc.messages.length) {

        doc.messageCount = doc.messages.length;

        doc.save(next);

    }

    next();

});

postSchema.virtual('id').get(function () {

    return this._id.toHexString();

});

postSchema.set('toJSON', {
    'virtuals': true
});

module.exports = mongoose.model('post', postSchema);
<|endoftext|>