<commit_before>import Ember from 'ember';
import DS from 'ember-data';

import {
  extractDeleteRecord
} from '../utils';

const {
  getOwner,
  run: {
    bind
  },
  on,
  String: {
    pluralize,
    camelize,
    classify
  }
} = Ember;

export default DS.RESTAdapter.extend({
  coalesceFindRequests: true,

  // The change listener ensures that individual records are kept up to date
  // when the data in the database changes. This makes ember-data 2.0's record
  // reloading redundant.
  shouldReloadRecord: function () { return false; },
  shouldBackgroundReloadRecord: function () { return false; },
  _onInit : on('init', function()  {
    this._startChangesToStoreListener();
  }),
  _startChangesToStoreListener: function () {
    var db = this.get('db');
    if (db) {
      this.changes = db.changes({
        since: 'now',
        live: true,
        returnDocs: false
      }).on('change', bind(this, 'onChange'));
    }
  },
  changeDb: function(db) {
    if (this.changes) {
      this.changes.cancel();
    }

    var store = this.store;
    var schema = this._schema || [];

    for (var i = 0, len = schema.length; i < len; i++) {
      store.unloadAll(schema[i].singular);
    }

    this._schema = null;
    this.set('db', db);
    this._startChangesToStoreListener();
  },
  onChange: function (change) {
    // If relational_pouch isn't initialized yet, there can't be any records
    // in the store to update.
    if (!this.get('db').rel) { return; }

    var obj = this.get('db').rel.parseDocID(change.id);
    // skip changes for non-relational_pouch docs. E.g., design docs.
    if (!obj.type || !obj.id || obj.type === '') { return; }

    var store = this.store;

    try {
      store.modelFor(obj.type);
    } catch (e) {
      // The record refers to a model which this version of the application
      // does not have.
      return;
    }

    var recordInStore = store.peekRecord(obj.type, obj.id);
    if (!recordInStore) {
      // The record hasn't been loaded into the store; no need to reload its data.
      this.unloadedDocumentChanged(obj);
      return;
    }
    if (!recordInStore.get('isLoaded') || recordInStore.get('hasDirtyAttributes')) {
      // The record either hasn't loaded yet or has unpersisted local changes.
      // In either case, we don't want to refresh it in the store
      // (and for some substates, attempting to do so will result in an error).
      return;
    }

    if (change.deleted) {
      store.unloadRecord(recordInStore);
    } else {
      recordInStore.reload();
    }
  },

  unloadedDocumentChanged: function(/* obj */) {
    /*
     * For performance purposes, we don't load records into the store that haven't previously been loaded.
     * If you want to change this, subclass this method, and push the data into the store. e.g.
     *
     *  let store = this.get('store');
     *  let recordTypeName = this.getRecordTypeName(store.modelFor(obj.type));
     *  this.get('db').rel.find(recordTypeName, obj.id).then(function(doc){
     *    store.pushPayload(recordTypeName, doc);
     *  });
     */
  },

  willDestroy: function() {
    if (this.changes) {
      this.changes.cancel();
    }
  },

  _init: function (store, type) {
    var self = this,
        recordTypeName = this.getRecordTypeName(type);
    if (!this.get('db') || typeof this.get('db') !== 'object') {
      throw new Error('Please set the `db` property on the adapter.');
    }

    if (!Ember.get(type, 'attributes').has('rev')) {
      var modelName = classify(recordTypeName);
      throw new Error('Please add a `rev` attribute of type `string`' +
        ' on the ' + modelName + ' model.');
    }

    this._schema = this._schema || [];

    var singular = recordTypeName;
    var plural = pluralize(recordTypeName);

    // check that we haven't already registered this model
    for (var i = 0, len = this._schema.length; i < len; i++) {
      var currentSchemaDef = this._schema[i];
      if (currentSchemaDef.singular === singular) {
        return;
      }
    }

    var schemaDef = {
      singular: singular,
      plural: plural
    };

    if (type.documentType) {
      schemaDef['documentType'] = type.documentType;
    }

    let config = getOwner(this).resolveRegistration('config:environment');
    let dontsavedefault = config['emberpouch'] && config['emberpouch']['dontsavehasmany'];
    // else it's new, so update
    this._schema.push(schemaDef);
    // check all the subtypes
    // We check the type of `rel.type`because with ember-data beta 19
    // `rel.type` switched from DS.Model to string
    type.eachRelationship(function (_, rel) {
      if (rel.kind !== 'belongsTo' && rel.kind !== 'hasMany') {
        // TODO: support inverse as well
        return; // skip
      }
      var relDef = {},
          relModel = (typeof rel.type === 'string' ? store.modelFor(rel.type) : rel.type);
      if (relModel) {
      	let includeRel = true;
      	rel.options = rel.options || {};
      	if (typeof(rel.options.async) === "undefined") {
      		rel.options.async = config.emberpouch && !Ember.isEmpty(config.emberpouch.async) ? config.emberpouch.async : true;//default true from https://github.com/emberjs/data/pull/3366
      	}
      	let options = Object.create(rel.options);
        if (rel.kind === 'hasMany' && (options.dontsave || typeof(options.dontsave) === 'undefined' && dontsavedefault)) {
        	let inverse = type.inverseFor(rel.key, store);
        	if (inverse) {
	        	if (inverse.kind === 'belongsTo') {
	        		self.get('db').createIndex({index: { fields: ['data.' + inverse.name, '_id'] }});
	        		if (options.async) {
	        			includeRel = false;
	        		} else {
	        			options.queryInverse = inverse.name;
	        		}
	        	} else {
	        		console.warn(type.modelName + " has a relationship with name " + rel.key + " that is many to many with type " + rel.type + ". This is not supported");
	        	}
	        } else {
	        	console.warn(type.modelName + " has a hasMany relationship with name " + rel.key + " that has no inverse.");
	        }
        }

        if (includeRel) {
	        relDef[rel.kind] = {
	          type: self.getRecordTypeName(relModel),
	          options: options
	        };
	        if (!schemaDef.relations) {
	          schemaDef.relations = {};
	        }
	        schemaDef.relations[rel.key] = relDef;
	    }
        self._init(store, relModel);
      }
    });

    this.get('db').setSchema(this._schema);
  },

  _recordToData: function (store, type, record) {
    var data = {};
    // Though it would work to use the default recordTypeName for modelName &
    // serializerKey here, these uses are conceptually distinct and may vary
    // independently.
    var modelName = type.modelName || type.typeKey;
    var serializerKey = camelize(modelName);
    var serializer = store.serializerFor(modelName);

    serializer.serializeIntoHash(
      data,
      type,
      record,
      {includeId: true}
    );

    data = data[serializerKey];

    // ember sets it to null automatically. don't need it.
    if (data.rev === null) {
      delete data.rev;
    }

    return data;
  },

  /**
   * Return key that conform to data adapter
   * ex: 'name' become 'data.name'
   */
  _dataKey: function(key) {
    var dataKey ='data.' + key;
    return ""+ dataKey + "";
  },

  /**
   * Returns the modified selector key to comform data key
   * Ex: selector: {name: 'Mario'} wil become selector: {'data.name': 'Mario'}
   */
  _buildSelector: function(selector) {
    var dataSelector = {};
    var selectorKeys = [];

    for (var key in selector) {
      if(selector.hasOwnProperty(key)){
        selectorKeys.push(key);
      }
    }

    selectorKeys.forEach(function(key) {
      var dataKey = this._dataKey(key);
      dataSelector[dataKey] = selector[key];
    }.bind(this));

    return dataSelector;
  },

  /**
   * Returns the modified sort key
   * Ex: sort: ['series'] will become ['data.series']
   * Ex: sort: [{series: 'desc'}] will became [{'data.series': 'desc'}]
   */
  _buildSort: function(sort) {
    return sort.map(function (value) {
      var sortKey = {};
      if (typeof value === 'object' && value !== null) {
        for (var key in value) {
          if(value.hasOwnProperty(key)){
            sortKey[this._dataKey(key)] = value[key];
          }
        }
      } else {
        return this._dataKey(value);
      }
      return sortKey;
    }.bind(this));
  },

  /**
   * Returns the string to use for the model name part of the PouchDB document
   * ID for records of the given ember-data type.
   *
   * This method uses the camelized version of the model name in order to
   * preserve data compatibility with older versions of ember-pouch. See
   * nolanlawson/ember-pouch#63 for a discussion.
   *
   * You can override this to change the behavior. If you do, be aware that you
   * need to execute a data migration to ensure that any existing records are
   * moved to the new IDs.
   */
  getRecordTypeName(type) {
    return camelize(type.modelName);
  },

  findAll: function(store, type /*, sinceToken */) {
    // TODO: use sinceToken
    this._init(store, type);
    return this.get('db').rel.find(this.getRecordTypeName(type));
  },

  findMany: function(store, type, ids) {
    this._init(store, type);
    return this.get('db').rel.find(this.getRecordTypeName(type), ids);
  },

  findHasMany: function(store, record, link, rel) {
  	let inverse = record.type.inverseFor(rel.key, store);
  	if (inverse && inverse.kind === 'belongsTo') {
  		return this.get('db').rel.findHasMany(camelize(rel.type), inverse.name, record.id);
	}
	else {
		console.warn("Can't find " + rel.key);
  		let result = {};
  		result[pluralize(rel.type)] = [];
  		return result;//data;
  	}
  },

  query: function(store, type, query) {
    this._init(store, type);

    var recordTypeName = this.getRecordTypeName(type);
    var db = this.get('db');

    var queryParams = {
      selector: this._buildSelector(query.filter)
    };

    if (!Ember.isEmpty(query.sort)) {
      queryParams.sort = this._buildSort(query.sort);
    }

    return db.find(queryParams).then(pouchRes => db.rel.parseRelDocs(recordTypeName, pouchRes.docs));
  },

  queryRecord: function(store, type, query) {
    return this.query(store, type, query).then(results => {
      let result = {};
      let recordType = this.getRecordTypeName(type);
      if(results[pluralize(recordType)].length > 0){
        result[recordType] = results[pluralize(recordType)][0];
      } else {
        result[recordType] = null;
      }
      return result;
    });
  },

  /**
   * `find` has been deprecated in ED 1.13 and is replaced by 'new store
   * methods', see: https://github.com/emberjs/data/pull/3306
   * We keep the method for backward compatibility and forward calls to
   * `findRecord`. This can be removed when the library drops support
   * for deprecated methods.
  */
  find: function (store, type, id) {
    return this.findRecord(store, type, id);
  },

  findRecord: function (store, type, id) {
    this._init(store, type);
    var recordTypeName = this.getRecordTypeName(type);
    return this.get('db').rel.find(recordTypeName, id).then(function (payload) {
      // Ember Data chokes on empty payload, this function throws
      // an error when the requested data is not found
      if (typeof payload === 'object' && payload !== null) {
        var singular = recordTypeName;
        var plural = pluralize(recordTypeName);

        var results = payload[singular] || payload[plural];
        if (results && results.length > 0) {
          return payload;
        }
      }
      throw new Error('Not found: type "' + recordTypeName +
        '" with id "' + id + '"');
    });
  },

  createRecord: function(store, type, record) {
    this._init(store, type);
    var data = this._recordToData(store, type, record);
    return this.get('db').rel.save(this.getRecordTypeName(type), data);
  },

  updateRecord: function (store, type, record) {
    this._init(store, type);
    var data = this._recordToData(store, type, record);
    return this.get('db').rel.save(this.getRecordTypeName(type), data);
  },

  deleteRecord: function (store, type, record) {
    this._init(store, type);
    var data = this._recordToData(store, type, record);
    return this.get('db').rel.del(this.getRecordTypeName(type), data)
      .then(extractDeleteRecord);
  }
});
<commit_msg>queryRecord returns single record with sideloading still available<commit_after>import Ember from 'ember';
import DS from 'ember-data';

import {
  extractDeleteRecord
} from '../utils';

const {
  getOwner,
  run: {
    bind
  },
  on,
  String: {
    pluralize,
    camelize,
    classify
  }
} = Ember;

export default DS.RESTAdapter.extend({
  coalesceFindRequests: true,

  // The change listener ensures that individual records are kept up to date
  // when the data in the database changes. This makes ember-data 2.0's record
  // reloading redundant.
  shouldReloadRecord: function () { return false; },
  shouldBackgroundReloadRecord: function () { return false; },
  _onInit : on('init', function()  {
    this._startChangesToStoreListener();
  }),
  _startChangesToStoreListener: function () {
    var db = this.get('db');
    if (db) {
      this.changes = db.changes({
        since: 'now',
        live: true,
        returnDocs: false
      }).on('change', bind(this, 'onChange'));
    }
  },
  changeDb: function(db) {
    if (this.changes) {
      this.changes.cancel();
    }

    var store = this.store;
    var schema = this._schema || [];

    for (var i = 0, len = schema.length; i < len; i++) {
      store.unloadAll(schema[i].singular);
    }

    this._schema = null;
    this.set('db', db);
    this._startChangesToStoreListener();
  },
  onChange: function (change) {
    // If relational_pouch isn't initialized yet, there can't be any records
    // in the store to update.
    if (!this.get('db').rel) { return; }

    var obj = this.get('db').rel.parseDocID(change.id);
    // skip changes for non-relational_pouch docs. E.g., design docs.
    if (!obj.type || !obj.id || obj.type === '') { return; }

    var store = this.store;

    try {
      store.modelFor(obj.type);
    } catch (e) {
      // The record refers to a model which this version of the application
      // does not have.
      return;
    }

    var recordInStore = store.peekRecord(obj.type, obj.id);
    if (!recordInStore) {
      // The record hasn't been loaded into the store; no need to reload its data.
      this.unloadedDocumentChanged(obj);
      return;
    }
    if (!recordInStore.get('isLoaded') || recordInStore.get('hasDirtyAttributes')) {
      // The record either hasn't loaded yet or has unpersisted local changes.
      // In either case, we don't want to refresh it in the store
      // (and for some substates, attempting to do so will result in an error).
      return;
    }

    if (change.deleted) {
      store.unloadRecord(recordInStore);
    } else {
      recordInStore.reload();
    }
  },

  unloadedDocumentChanged: function(/* obj */) {
    /*
     * For performance purposes, we don't load records into the store that haven't previously been loaded.
     * If you want to change this, subclass this method, and push the data into the store. e.g.
     *
     *  let store = this.get('store');
     *  let recordTypeName = this.getRecordTypeName(store.modelFor(obj.type));
     *  this.get('db').rel.find(recordTypeName, obj.id).then(function(doc){
     *    store.pushPayload(recordTypeName, doc);
     *  });
     */
  },

  willDestroy: function() {
    if (this.changes) {
      this.changes.cancel();
    }
  },

  _init: function (store, type) {
    var self = this,
        recordTypeName = this.getRecordTypeName(type);
    if (!this.get('db') || typeof this.get('db') !== 'object') {
      throw new Error('Please set the `db` property on the adapter.');
    }

    if (!Ember.get(type, 'attributes').has('rev')) {
      var modelName = classify(recordTypeName);
      throw new Error('Please add a `rev` attribute of type `string`' +
        ' on the ' + modelName + ' model.');
    }

    this._schema = this._schema || [];

    var singular = recordTypeName;
    var plural = pluralize(recordTypeName);

    // check that we haven't already registered this model
    for (var i = 0, len = this._schema.length; i < len; i++) {
      var currentSchemaDef = this._schema[i];
      if (currentSchemaDef.singular === singular) {
        return;
      }
    }

    var schemaDef = {
      singular: singular,
      plural: plural
    };

    if (type.documentType) {
      schemaDef['documentType'] = type.documentType;
    }

    let config = getOwner(this).resolveRegistration('config:environment');
    let dontsavedefault = config['emberpouch'] && config['emberpouch']['dontsavehasmany'];
    // else it's new, so update
    this._schema.push(schemaDef);
    // check all the subtypes
    // We check the type of `rel.type`because with ember-data beta 19
    // `rel.type` switched from DS.Model to string
    type.eachRelationship(function (_, rel) {
      if (rel.kind !== 'belongsTo' && rel.kind !== 'hasMany') {
        // TODO: support inverse as well
        return; // skip
      }
      var relDef = {},
          relModel = (typeof rel.type === 'string' ? store.modelFor(rel.type) : rel.type);
      if (relModel) {
      	let includeRel = true;
      	rel.options = rel.options || {};
      	if (typeof(rel.options.async) === "undefined") {
      		rel.options.async = config.emberpouch && !Ember.isEmpty(config.emberpouch.async) ? config.emberpouch.async : true;//default true from https://github.com/emberjs/data/pull/3366
      	}
      	let options = Object.create(rel.options);
        if (rel.kind === 'hasMany' && (options.dontsave || typeof(options.dontsave) === 'undefined' && dontsavedefault)) {
        	let inverse = type.inverseFor(rel.key, store);
        	if (inverse) {
	        	if (inverse.kind === 'belongsTo') {
	        		self.get('db').createIndex({index: { fields: ['data.' + inverse.name, '_id'] }});
	        		if (options.async) {
	        			includeRel = false;
	        		} else {
	        			options.queryInverse = inverse.name;
	        		}
	        	} else {
	        		console.warn(type.modelName + " has a relationship with name " + rel.key + " that is many to many with type " + rel.type + ". This is not supported");
	        	}
	        } else {
	        	console.warn(type.modelName + " has a hasMany relationship with name " + rel.key + " that has no inverse.");
	        }
        }

        if (includeRel) {
	        relDef[rel.kind] = {
	          type: self.getRecordTypeName(relModel),
	          options: options
	        };
	        if (!schemaDef.relations) {
	          schemaDef.relations = {};
	        }
	        schemaDef.relations[rel.key] = relDef;
	    }
        self._init(store, relModel);
      }
    });

    this.get('db').setSchema(this._schema);
  },

  _recordToData: function (store, type, record) {
    var data = {};
    // Though it would work to use the default recordTypeName for modelName &
    // serializerKey here, these uses are conceptually distinct and may vary
    // independently.
    var modelName = type.modelName || type.typeKey;
    var serializerKey = camelize(modelName);
    var serializer = store.serializerFor(modelName);

    serializer.serializeIntoHash(
      data,
      type,
      record,
      {includeId: true}
    );

    data = data[serializerKey];

    // ember sets it to null automatically. don't need it.
    if (data.rev === null) {
      delete data.rev;
    }

    return data;
  },

  /**
   * Return key that conform to data adapter
   * ex: 'name' become 'data.name'
   */
  _dataKey: function(key) {
    var dataKey ='data.' + key;
    return ""+ dataKey + "";
  },

  /**
   * Returns the modified selector key to comform data key
   * Ex: selector: {name: 'Mario'} wil become selector: {'data.name': 'Mario'}
   */
  _buildSelector: function(selector) {
    var dataSelector = {};
    var selectorKeys = [];

    for (var key in selector) {
      if(selector.hasOwnProperty(key)){
        selectorKeys.push(key);
      }
    }

    selectorKeys.forEach(function(key) {
      var dataKey = this._dataKey(key);
      dataSelector[dataKey] = selector[key];
    }.bind(this));

    return dataSelector;
  },

  /**
   * Returns the modified sort key
   * Ex: sort: ['series'] will become ['data.series']
   * Ex: sort: [{series: 'desc'}] will became [{'data.series': 'desc'}]
   */
  _buildSort: function(sort) {
    return sort.map(function (value) {
      var sortKey = {};
      if (typeof value === 'object' && value !== null) {
        for (var key in value) {
          if(value.hasOwnProperty(key)){
            sortKey[this._dataKey(key)] = value[key];
          }
        }
      } else {
        return this._dataKey(value);
      }
      return sortKey;
    }.bind(this));
  },

  /**
   * Returns the string to use for the model name part of the PouchDB document
   * ID for records of the given ember-data type.
   *
   * This method uses the camelized version of the model name in order to
   * preserve data compatibility with older versions of ember-pouch. See
   * nolanlawson/ember-pouch#63 for a discussion.
   *
   * You can override this to change the behavior. If you do, be aware that you
   * need to execute a data migration to ensure that any existing records are
   * moved to the new IDs.
   */
  getRecordTypeName(type) {
    return camelize(type.modelName);
  },

  findAll: function(store, type /*, sinceToken */) {
    // TODO: use sinceToken
    this._init(store, type);
    return this.get('db').rel.find(this.getRecordTypeName(type));
  },

  findMany: function(store, type, ids) {
    this._init(store, type);
    return this.get('db').rel.find(this.getRecordTypeName(type), ids);
  },

  findHasMany: function(store, record, link, rel) {
  	let inverse = record.type.inverseFor(rel.key, store);
  	if (inverse && inverse.kind === 'belongsTo') {
  		return this.get('db').rel.findHasMany(camelize(rel.type), inverse.name, record.id);
	}
	else {
		console.warn("Can't find " + rel.key);
  		let result = {};
  		result[pluralize(rel.type)] = [];
  		return result;//data;
  	}
  },

  query: function(store, type, query) {
    this._init(store, type);

    var recordTypeName = this.getRecordTypeName(type);
    var db = this.get('db');

    var queryParams = {
      selector: this._buildSelector(query.filter)
    };

    if (!Ember.isEmpty(query.sort)) {
      queryParams.sort = this._buildSort(query.sort);
    }

    return db.find(queryParams).then(pouchRes => db.rel.parseRelDocs(recordTypeName, pouchRes.docs));
  },

  queryRecord: function(store, type, query) {
    return this.query(store, type, query).then(results => {
      let recordType = this.getRecordTypeName(type);
      let recordTypePlural = pluralize(recordType);
      if(results[recordTypePlural].length > 0){
        results[recordType] = results[recordTypePlural][0];
      } else {
        results[recordType] = null;
      }
      delete results[recordTypePlural];
      return results;
    });
  },

  /**
   * `find` has been deprecated in ED 1.13 and is replaced by 'new store
   * methods', see: https://github.com/emberjs/data/pull/3306
   * We keep the method for backward compatibility and forward calls to
   * `findRecord`. This can be removed when the library drops support
   * for deprecated methods.
  */
  find: function (store, type, id) {
    return this.findRecord(store, type, id);
  },

  findRecord: function (store, type, id) {
    this._init(store, type);
    var recordTypeName = this.getRecordTypeName(type);
    return this.get('db').rel.find(recordTypeName, id).then(function (payload) {
      // Ember Data chokes on empty payload, this function throws
      // an error when the requested data is not found
      if (typeof payload === 'object' && payload !== null) {
        var singular = recordTypeName;
        var plural = pluralize(recordTypeName);

        var results = payload[singular] || payload[plural];
        if (results && results.length > 0) {
          return payload;
        }
      }
      throw new Error('Not found: type "' + recordTypeName +
        '" with id "' + id + '"');
    });
  },

  createRecord: function(store, type, record) {
    this._init(store, type);
    var data = this._recordToData(store, type, record);
    return this.get('db').rel.save(this.getRecordTypeName(type), data);
  },

  updateRecord: function (store, type, record) {
    this._init(store, type);
    var data = this._recordToData(store, type, record);
    return this.get('db').rel.save(this.getRecordTypeName(type), data);
  },

  deleteRecord: function (store, type, record) {
    this._init(store, type);
    var data = this._recordToData(store, type, record);
    return this.get('db').rel.del(this.getRecordTypeName(type), data)
      .then(extractDeleteRecord);
  }
});
<|endoftext|>


<commit_before>/*
 * script dependencies
 *
 * jQuery
 * socket.io
 */

$(document).ready( function($) {
  var socket = io();

  var $loginWrap = $('#login-wrap')
    , $appWrap = $('#app-wrap')
    , $loginError = $('#login-error-display')
    , $messageForm = $('#send-message')
    , $loginForm = $('#send-login')
    , $usernameBox = $('#username-box')
    , $messageBox = $('#message-box')
    , $messageDisplay = $('#chat-display')
    , $userlist = $('#userlist')
  ;

  // Start in login view.
  $appWrap.hide();

  $loginWrap.show();

  $loginForm.submit(function(_event) {
    _event.preventDefault();

    socket.emit('user-login', {
      'time': Date.now()
    , 'user': $usernameBox.val()
    });

  });

  $messageForm.submit(function(_event) {
    _event.preventDefault();

    var message = rp.msgCmd.parser.parseMessage($messageBox.val());
    message['time'] = Date.now();

    socket.emit('message-server', message);

    $messageBox.val('');
  });

  // Userlist
  var userlist = new rp.html.ListDiv($userlist.prop('id'), {});
  userlist.initHtml();

  socket.on('user-login-response', function(_data) {
    if(_data.successful === true) {
      // Switch to chat view.
      $loginWrap.hide();
      $appWrap.show();

    } else {
      $loginError.text(_data.error);
    }
  });

  socket.on('message-client', function(_data) {
    if(_data.error) {
      $messageDisplay.append('
' + '' + _data.error + '' + '
');
      return;
    }

    if(_data.commands && _data.commands.length > 0) {
      $messageDisplay.append( displayCommandsReceived(_data) );
    }

    $messageDisplay.append('
' + '' + _data.user + ': ' + '' + _data.message + '
');
  });

  socket.on('userlist', function(_data) {
    for(var i = 0; i < _data.users.length; i++) {
      userlist.addToList(_data.users[i].name);
    }

    return;
  });

  socket.on('user-signing', function(_data) {
    if(_data.type === 'SIGNIN') {
      userlist.addToList(_data.user);
      $messageDisplay.append('
' + '' + _data.user + ' joined' + '' + '
');
    } else if (_data.type === 'SIGNOUT') {
      userlist.removeFromList(_data.user);
      $messageDisplay.append('
' + '' + _data.user + ' left' + '' + '
');
    }
  });
});

function displayCommandsReceived(_data) {
  var commandsHtml = '';

  for(var i = 0; i < _data.commands.length; i++) {
    commandsHtml += '
' + '' + 'Command ' + _data.commands[i].cmd + ' received from ' + _data.user + '' + '
';
  }

  return commandsHtml;
}<commit_msg>Moved function in chat.js from outside .ready( function($) { to inside it.<commit_after>/*
 * script dependencies
 *
 * jQuery
 * socket.io
 */

$(document).ready( function($) {
  var socket = io();

  var $loginWrap = $('#login-wrap')
    , $appWrap = $('#app-wrap')
    , $loginError = $('#login-error-display')
    , $messageForm = $('#send-message')
    , $loginForm = $('#send-login')
    , $usernameBox = $('#username-box')
    , $messageBox = $('#message-box')
    , $messageDisplay = $('#chat-display')
    , $userlist = $('#userlist')
  ;

  // Start in login view.
  $appWrap.hide();

  $loginWrap.show();

  $loginForm.submit(function(_event) {
    _event.preventDefault();

    socket.emit('user-login', {
      'time': Date.now()
    , 'user': $usernameBox.val()
    });

  });

  $messageForm.submit(function(_event) {
    _event.preventDefault();

    var message = rp.msgCmd.parser.parseMessage($messageBox.val());
    message['time'] = Date.now();

    socket.emit('message-server', message);

    $messageBox.val('');
  });

  // Userlist
  var userlist = new rp.html.ListDiv($userlist.prop('id'), {});
  userlist.initHtml();

  socket.on('user-login-response', function(_data) {
    if(_data.successful === true) {
      // Switch to chat view.
      $loginWrap.hide();
      $appWrap.show();

    } else {
      $loginError.text(_data.error);
    }
  });

  socket.on('message-client', function(_data) {
    if(_data.error) {
      $messageDisplay.append('
' + '' + _data.error + '' + '
');
      return;
    }

    if(_data.commands && _data.commands.length > 0) {
      $messageDisplay.append( displayCommandsReceived(_data) );
    }

    $messageDisplay.append('
' + '' + _data.user + ': ' + '' + _data.message + '
');
  });

  socket.on('userlist', function(_data) {
    for(var i = 0; i < _data.users.length; i++) {
      userlist.addToList(_data.users[i].name);
    }

    return;
  });

  socket.on('user-signing', function(_data) {
    if(_data.type === 'SIGNIN') {
      userlist.addToList(_data.user);
      $messageDisplay.append('
' + '' + _data.user + ' joined' + '' + '
');
    } else if (_data.type === 'SIGNOUT') {
      userlist.removeFromList(_data.user);
      $messageDisplay.append('
' + '' + _data.user + ' left' + '' + '
');
    }
  });

  function displayCommandsReceived(_data) {
  var commandsHtml = '';

  for(var i = 0; i < _data.commands.length; i++) {
    commandsHtml += '
' + '' + 'Command ' + _data.commands[i].cmd + ' received from ' + _data.user + '' + '
';
  }

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


<commit_before>'use strict'

angular.module('ANNO.directives', [])
.directive('inputEnter', function() {
  var ENTER_KEY = 13
  return function (scope, elem, attrs) {
    elem.bind('keydown', function (event) {
      if (event.keyCode === ENTER_KEY) {
        scope.$apply(attrs.inputEnter)
      }
    })
  }
})
.directive('editor', ['$modal', function($modal) {
  return {
    restrict: 'E',
    replace: true,
    templateUrl: '/partials/widgets/editor.html',
    scope: {
      article: "=", // data bind this property with a property in parent scope
      images: "="
    },
    controller: function($scope) {
      var utils = {
        bold: function(s, editor) {
          if (s.charAt(0) === '*' && s.charAt(s.length - 1 === '*')) {
            editor.replaceSelection(s.replace(/\*/g, ''));
          } else {
            editor.replaceSelection('**' + s.replace(/\*/g, '') + '**');
          }
        },
        italic: function(s, editor) {
          if (s.charAt(0) === '_' && s.charAt(s.length - 1 === '_')) {
            editor.replaceSelection(s.replace(/_/g, ''));
          } else {
            editor.replaceSelection('_' + s.replace(/_/g, '') + '_');
          }
        },
        quote: function(s, editor) {
          if (s.charAt(0) === '>') {
            editor.replaceSelection(util.lTrim(s.replace(/\>/g, '')));
          } else {
            editor.replaceSelection('> ' + s.replace(/\>/g, ''));
          }
        }
      }
      var self = this
      this.codeMirrorLoad = function(editor) {
        $scope.bold = function() {
          if (editor.getSelection() !== '') utils.bold(editor.getSelection(), editor)
        }
        $scope.italic = function() {
          if (editor.getSelection() !== '') utils.italic(editor.getSelection(), editor)
        }
        $scope.quote = function() {
          if (editor.getSelection() !== '') utils.quote(editor.getSelection(), editor)
        }
        self.uploadPhoto = function(dataURL, file) {
          $scope.article.last_photo = $scope.article.last_photo || 0
          ++$scope.article.last_photo

          $scope.images.push({
            name: 'pic' + $scope.article.last_photo,
            src: dataURL,
            obj: file
          })

          editor.replaceSelection('<图片' + $scope.article.last_photo + '>')
        }
        $scope.toggleEditorHelp = function() {
          var modalInstance = $modal.open({
            templateUrl: 'modalEditorHelp.html',
            controller: function($scope, $modalInstance) {
              $scope.ok = function (bid) { $modalInstance.close() }
            }
          })
        }

      }

    }
  }
}])
.directive('codemirror', function() {
  return {
    restrict: 'E',
    require: ['^editor', 'ngModel'],
    replace: true,
    link: function(scope, elem, attrs, ctrls) {
      var editorCtrl = ctrls[0]
        , ngModel = ctrls[1]

      var editor = new CodeMirror(elem[0], {
        mode: 'gfm',
        // value: 'initial text\n\n```javascript\nconsole.log("simon")\n```',
        lineWrapping: true,
        lineNumbers: false,
        matchBrackets: true,
        autofocus: true,
        theme: 'prose-bright',
        placeholder: '开始写笔记'
      })
      editor.refresh()
      editor.on('change', function(cm) {
        var phase = scope.$root.$$phase
        if(phase == '$apply' || phase == '$digest') {
          ngModel.$setViewValue(cm.getValue())
        } else {
          scope.$apply(function(){
            ngModel.$setViewValue(cm.getValue())
          })
        }
        // scope.$apply(function() {
        //   ngModel.$setViewValue(cm.getValue())
        // })
      })
      ngModel.$render = function() {
        if (!ngModel.$viewValue) return
        editor.setValue(ngModel.$viewValue)
      }

      editorCtrl.codeMirrorLoad(editor)
      //ngModel.$setViewValue(editor.getValue())
    }
  }
})
.directive('filereader', function() {
  return {
    restrict: 'A',
    require: '^editor',
    link: function(scope, elem, attrs, editorCtrl) {
      var reader = new FileReader()
      reader.onload = function (e) {
        editorCtrl.uploadPhoto(e.target.result, elem[0].files[0])
      }

      elem.on('change', function() {
        reader.readAsDataURL(elem[0].files[0])
      })
    }
  }
})
.directive('reader', function() {
  return {
    restrict: 'E',
    replace: true,
    templateUrl: '/partials/widgets/reader.html',
    scope: {
      article: "=",
      images: "="
    },
    controller: function($scope) {
      this.images = $scope.images
    }
  }
})
.directive('previewer', ['$timeout', '$http', function($timeout, $http) {
  var imageCache = {}
  return {
    restrict: 'E',
    scope: {
      markdown: '=',
      images: '='
    },
    template: '
{{markdown}}
',
    replace: true,
    require: '^?reader',
    link: function(scope, elem, attrs, readerCtrl) {
      var renderer = new marked.Renderer()
      renderer.image = function (href, title, text) {
        var out = '' : '>'
        return out
      }
      renderer.link = function(href, title, text) {
        if (this.options.sanitize) {
          try {
            var prot = decodeURIComponent(unescape(href))
                       .replace(/[^\w:]/g, '')
                       .toLowerCase();
          } catch (e) {
            return '';
          }
          if (prot.indexOf('javascript:') === 0) {
            return '';
          }
        }
        var out = '' + text + '';
        return out;
      }
      marked.setOptions({
        renderer: renderer,
        gfm: true,
        tables: true,
        breaks: true,
        pedantic: false,
        sanitize: true,
        smartLists: true,
        smartypants: false
      })
      scope.$watch('markdown', function(text) {
        if (_.isUndefined(text)) return

        elem.html(marked(text))
        // $compile(elem.html())(scope)

        var MIME = {
          'c': 'text/x-csrc',
          'cpp': 'text/x-c++src',
          'csharp': 'text/x-csharp',
          'java': 'text/x-java',
          'lisp': 'text/x-common-lisp'
        }
        Array.prototype.slice.call(elem.find('pre'), 0).forEach(function(block) {
          var $block = $(block)
            , $code = $block.find('code')
          if ($code.attr('class').length) {
            var mode = $code.attr('class').slice(5)
            mode = MIME[mode] || mode
            CodeMirror.runMode($block.text(), mode, block)
            $block.addClass('cm-s-prose-bright')
          }
        })

        // existed image
        Array.prototype.slice.call(elem.find('img'), 0).forEach(function(img){
          var $img = $(img)
            , source = $img.attr('data-src')
          if (!imageCache[source]) {
            $http.get($img.attr('data-src'), { 'responseType': 'blob' , 'cache': true}).success(function(blob) {
              imageCache[source] = window.URL.createObjectURL(blob)
              // $(img) becomes a document fragment, confused..
              // console.log($img)
              // console.log(elem)
              // console.log(elem.find('img'))
              Array.prototype.slice.call(elem.find('img'), 0).forEach(function(new_img) {
                $(new_img).attr('src', imageCache[source])
              })
            })
          } else {
            $img.attr('src', imageCache[source])
          }
        })
        // inserted image
        elem.html(elem.html().replace(/\<图片(\d+)\>/g, function(wholeMatch, m1) {
          var img = _.findWhere(scope.images, { name: 'pic' + m1 })
          return ''
        }))

      })

    }
  }
}])
.directive('loader', ['$rootScope', '$timeout', function($rootScope, $timeout) {
  return function(scope, elem, attrs) {
    scope.$on('loading:show', function() {
      elem.css('display', 'block').removeClass('fadeOut')
    })
    scope.$on('loading:hide', function() {
      elem.addClass('fadeOut')
      $timeout(function() {
        elem.css('display', 'none')
      }, 1000)
    })
  }
}])
.directive('alert', ['$rootScope', '$timeout', function($rootScope, $timeout) {
  return function(scope, elem, attrs) {
    var ERROR = {
        '103': '访问令牌出错，需要重新授权登录',
        '111': '访问太频繁，超出第三方应用限额',
        '999': '未知错误',
        '1000': '这篇笔记是私密的，只能阅读到简介部分，不能显示全文',
        '1001': '内容不存在，回到我的书架',
        '1002': '必要的信息还没填完，请检查标题和内容是否填写',
        '1003': '上传图片太大，不能大于3M',
        '1004': '有违禁词',
        '1005': '输入的正文太短，要求超过15个字',
        '1008': '不支持上传图片的格式'
    }
    scope.$on('alert:error', function(e, data) {
      var msg = ERROR[data.code]
      if (!msg) {
        if (data.code) {
          msg = '你遇到了编号为' + data.code + '的错误"' + data.msg
        } else {
          msg = '发生了奇怪的错误，可能是服务器不稳定，稍后再试试吧。告诉管理员让他修复错误吧。'
        }
      }
      elem.addClass('error').addClass('show').html(msg)
    })
    scope.$on('alert:success', function(e, msg) {
      elem.addClass('success').addClass('show').html(msg)
      $timeout(function() {
        scope.$emit('alert:dismiss')
      }, 2000)
    })
    elem.on('click', function() {
      elem.removeClass('show')
    })
    scope.$on('alert:dismiss', function(e) {
      elem.removeClass('show')
    })

    scope.$on('alert:error:hide', elem.removeClass.bind(elem, 'show'))
    $rootScope.$on('$routeChangeStart', scope.$emit.bind(scope, 'alert:error:hide'))
  }
}])
.directive('colorThief', ['$http', function($http) {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, elem, attrs, ngModel) {
      var colorThief = new ColorThief()
        , sourceImage = new Image

      scope.$watch(attrs.image, function(url) {
        if (!url) return
        $http.get(url, {responseType: 'blob', cache: 'true'}).success(function(blob) {
          sourceImage.src = window.URL.createObjectURL(blob)
        })
        sourceImage.onload = function() {
          var rgb = colorThief.getColor(sourceImage)
            , luma = 0.2126*rgb[0] + 0.7152*rgb[1] + 0.0722*rgb[2]
          ngModel.$setViewValue({
            'background': 'rgb(' + rgb.join(',') + ')',
            'foreground': luma>192? '#000': '#fff'
          })
        }
      })
    }
  }
}])
// packaged app specific directive
.directive("link", function ($location) {
  return function (scope, element, attrs) {
    element.bind("click", function () {
      scope.$apply($location.path(attrs.link))
    });
  }
})
.directive("remoteImage", ['$http', '$compile', function($http, $compile) {
  function fetchImage(scope, elem, url) {
    $http.get(url, {responseType: 'blob', cache: true}).success(function(blob) {
      if (elem[0].nodeName === 'IMG') {
        elem.attr('src', window.URL.createObjectURL(blob))

      } else {
        elem.css({
          'background-image': 'url(' + window.URL.createObjectURL(blob) + ')'
        })
      }
    })
  }
  return {
    link: function(scope, elem, attrs) {
      scope.$watch(attrs.remoteImage, function(url) {
        if (!url) return
        fetchImage(scope, elem, url)
      })
    }
  }
}])
<commit_msg>compile directive for dynamic use<commit_after>'use strict'

angular.module('ANNO.directives', [])
.directive('inputEnter', function() {
  var ENTER_KEY = 13
  return function (scope, elem, attrs) {
    elem.bind('keydown', function (event) {
      if (event.keyCode === ENTER_KEY) {
        scope.$apply(attrs.inputEnter)
      }
    })
  }
})
.directive('editor', ['$modal', function($modal) {
  return {
    restrict: 'E',
    replace: true,
    templateUrl: '/partials/widgets/editor.html',
    scope: {
      article: "=", // data bind this property with a property in parent scope
      images: "="
    },
    controller: function($scope) {
      var utils = {
        bold: function(s, editor) {
          if (s.charAt(0) === '*' && s.charAt(s.length - 1 === '*')) {
            editor.replaceSelection(s.replace(/\*/g, ''));
          } else {
            editor.replaceSelection('**' + s.replace(/\*/g, '') + '**');
          }
        },
        italic: function(s, editor) {
          if (s.charAt(0) === '_' && s.charAt(s.length - 1 === '_')) {
            editor.replaceSelection(s.replace(/_/g, ''));
          } else {
            editor.replaceSelection('_' + s.replace(/_/g, '') + '_');
          }
        },
        quote: function(s, editor) {
          if (s.charAt(0) === '>') {
            editor.replaceSelection(util.lTrim(s.replace(/\>/g, '')));
          } else {
            editor.replaceSelection('> ' + s.replace(/\>/g, ''));
          }
        }
      }
      var self = this
      this.codeMirrorLoad = function(editor) {
        $scope.bold = function() {
          if (editor.getSelection() !== '') utils.bold(editor.getSelection(), editor)
        }
        $scope.italic = function() {
          if (editor.getSelection() !== '') utils.italic(editor.getSelection(), editor)
        }
        $scope.quote = function() {
          if (editor.getSelection() !== '') utils.quote(editor.getSelection(), editor)
        }
        self.uploadPhoto = function(dataURL, file) {
          $scope.article.last_photo = $scope.article.last_photo || 0
          ++$scope.article.last_photo

          $scope.images.push({
            name: 'pic' + $scope.article.last_photo,
            src: dataURL,
            obj: file
          })

          editor.replaceSelection('<图片' + $scope.article.last_photo + '>')
        }
        $scope.toggleEditorHelp = function() {
          var modalInstance = $modal.open({
            templateUrl: 'modalEditorHelp.html',
            controller: function($scope, $modalInstance) {
              $scope.ok = function (bid) { $modalInstance.close() }
            }
          })
        }

      }

    }
  }
}])
.directive('codemirror', function() {
  return {
    restrict: 'E',
    require: ['^editor', 'ngModel'],
    replace: true,
    link: function(scope, elem, attrs, ctrls) {
      var editorCtrl = ctrls[0]
        , ngModel = ctrls[1]

      var editor = new CodeMirror(elem[0], {
        mode: 'gfm',
        // value: 'initial text\n\n```javascript\nconsole.log("simon")\n```',
        lineWrapping: true,
        lineNumbers: false,
        matchBrackets: true,
        autofocus: true,
        theme: 'prose-bright',
        placeholder: '开始写笔记'
      })
      editor.refresh()
      editor.on('change', function(cm) {
        var phase = scope.$root.$$phase
        if(phase == '$apply' || phase == '$digest') {
          ngModel.$setViewValue(cm.getValue())
        } else {
          scope.$apply(function(){
            ngModel.$setViewValue(cm.getValue())
          })
        }
        // scope.$apply(function() {
        //   ngModel.$setViewValue(cm.getValue())
        // })
      })
      ngModel.$render = function() {
        if (!ngModel.$viewValue) return
        editor.setValue(ngModel.$viewValue)
      }

      editorCtrl.codeMirrorLoad(editor)
      //ngModel.$setViewValue(editor.getValue())
    }
  }
})
.directive('filereader', function() {
  return {
    restrict: 'A',
    require: '^editor',
    link: function(scope, elem, attrs, editorCtrl) {
      var reader = new FileReader()
      reader.onload = function (e) {
        editorCtrl.uploadPhoto(e.target.result, elem[0].files[0])
      }

      elem.on('change', function() {
        reader.readAsDataURL(elem[0].files[0])
      })
    }
  }
})
.directive('reader', function() {
  return {
    restrict: 'E',
    replace: true,
    templateUrl: '/partials/widgets/reader.html',
    scope: {
      article: "=",
      images: "="
    },
    controller: function($scope) {
      this.images = $scope.images
    }
  }
})
.directive('previewer', ['$timeout', '$http', function($timeout, $http) {
  var imageCache = {}
  return {
    restrict: 'E',
    scope: {
      markdown: '=',
      images: '='
    },
    template: '
{{markdown}}
',
    replace: true,
    require: '^?reader',
    link: function(scope, elem, attrs, readerCtrl) {
      var renderer = new marked.Renderer()
      renderer.image = function (href, title, text) {
        var out = '' : '>'
        return out
      }
      renderer.link = function(href, title, text) {
        if (this.options.sanitize) {
          try {
            var prot = decodeURIComponent(unescape(href))
                       .replace(/[^\w:]/g, '')
                       .toLowerCase();
          } catch (e) {
            return '';
          }
          if (prot.indexOf('javascript:') === 0) {
            return '';
          }
        }
        var out = '' + text + '';
        return out;
      }
      marked.setOptions({
        renderer: renderer,
        gfm: true,
        tables: true,
        breaks: true,
        pedantic: false,
        sanitize: true,
        smartLists: true,
        smartypants: false
      })
      scope.$watch('markdown', function(text) {
        if (_.isUndefined(text)) return

        elem.html(marked(text))
        // $compile(elem.html())(scope)

        var MIME = {
          'c': 'text/x-csrc',
          'cpp': 'text/x-c++src',
          'csharp': 'text/x-csharp',
          'java': 'text/x-java',
          'lisp': 'text/x-common-lisp'
        }
        Array.prototype.slice.call(elem.find('pre'), 0).forEach(function(block) {
          var $block = $(block)
            , $code = $block.find('code')
          if ($code.attr('class').length) {
            var mode = $code.attr('class').slice(5)
            mode = MIME[mode] || mode
            CodeMirror.runMode($block.text(), mode, block)
            $block.addClass('cm-s-prose-bright')
          }
        })

        // existed image
        Array.prototype.slice.call(elem.find('img'), 0).forEach(function(img){
          var $img = $(img)
            , source = $img.attr('data-src')
          if (!imageCache[source]) {
            $http.get($img.attr('data-src'), { 'responseType': 'blob' , 'cache': true}).success(function(blob) {
              imageCache[source] = window.URL.createObjectURL(blob)
              // $(img) becomes a document fragment, confused..
              // console.log($img)
              // console.log(elem)
              // console.log(elem.find('img'))
              Array.prototype.slice.call(elem.find('img'), 0).forEach(function(new_img) {
                $(new_img).attr('src', imageCache[source])
              })
            })
          } else {
            $img.attr('src', imageCache[source])
          }
        })
        // inserted image
        elem.html(elem.html().replace(/\<图片(\d+)\>/g, function(wholeMatch, m1) {
          var img = _.findWhere(scope.images, { name: 'pic' + m1 })
          return ''
        }))

      })

    }
  }
}])
.directive('loader', ['$rootScope', '$timeout', function($rootScope, $timeout) {
  return function(scope, elem, attrs) {
    scope.$on('loading:show', function() {
      elem.css('display', 'block').removeClass('fadeOut')
    })
    scope.$on('loading:hide', function() {
      elem.addClass('fadeOut')
      $timeout(function() {
        elem.css('display', 'none')
      }, 1000)
    })
  }
}])
.directive('alert', ['$rootScope', '$timeout', '$compile', function($rootScope, $timeout, $compile) {
  return function(scope, elem, attrs) {
    var ERROR = {
        '103': '访问令牌出错，需要重新授权登录',
        '111': '访问太频繁，超出第三方应用限额',
        '999': '未知错误',
        '1000': '这篇笔记是私密的，只能阅读到简介部分，不能显示全文。点我返回书架',
        '1001': '内容不存在，回到我的书架',
        '1002': '必要的信息还没填完，请检查标题和内容是否填写',
        '1003': '上传图片太大，不能大于3M',
        '1004': '有违禁词',
        '1005': '输入的正文太短，要求超过15个字',
        '1008': '不支持上传图片的格式'
    }
    scope.$on('alert:error', function(e, data) {
      var msg = ERROR[data.code]
      if (!msg) {
        if (data.code) {
          msg = '你遇到了编号为' + data.code + '的错误"' + data.msg
        } else {
          msg = '发生了奇怪的错误，可能是服务器不稳定，稍后再试试吧。告诉管理员让他修复错误吧。'
        }
      }
      elem.addClass('error').addClass('show').html(msg)
      // compile link directive inside
      var link = elem.find('a')
      if (link.length) {
        link.replaceWith($compile(link[0].outerHTML)(scope))
      }
    })

    scope.$on('alert:success', function(e, msg) {
      elem.addClass('success').addClass('show').html(msg)
      $timeout(function() {
        scope.$emit('alert:dismiss')
      }, 2000)
    })

    scope.$on('alert:dismiss', function(e) {
      elem.removeClass('show')
    })

    elem.on('click', function(e) {
      elem.removeClass('show')
    })

    scope.$on('alert:error:hide', elem.removeClass.bind(elem, 'show'))
    $rootScope.$on('$routeChangeStart', scope.$emit.bind(scope, 'alert:error:hide'))
  }
}])
.directive('colorThief', ['$http', function($http) {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, elem, attrs, ngModel) {
      var colorThief = new ColorThief()
        , sourceImage = new Image

      scope.$watch(attrs.image, function(url) {
        if (!url) return
        $http.get(url, {responseType: 'blob', cache: 'true'}).success(function(blob) {
          sourceImage.src = window.URL.createObjectURL(blob)
        })
        sourceImage.onload = function() {
          var rgb = colorThief.getColor(sourceImage)
            , luma = 0.2126*rgb[0] + 0.7152*rgb[1] + 0.0722*rgb[2]
          ngModel.$setViewValue({
            'background': 'rgb(' + rgb.join(',') + ')',
            'foreground': luma>192? '#000': '#fff'
          })
        }
      })
    }
  }
}])
// packaged app specific directive
.directive("link", function ($location, $window) {
  return function (scope, element, attrs) {
    element.bind("click", function () {
      scope.$apply($location.path(attrs.link))
    });
  }
})
.directive("remoteImage", ['$http', function($http) {
  function fetchImage(scope, elem, url) {
    $http.get(url, {responseType: 'blob', cache: true}).success(function(blob) {
      if (elem[0].nodeName === 'IMG') {
        elem.attr('src', window.URL.createObjectURL(blob))

      } else {
        elem.css({
          'background-image': 'url(' + window.URL.createObjectURL(blob) + ')'
        })
      }
    })
  }
  return {
    link: function(scope, elem, attrs) {
      scope.$watch(attrs.remoteImage, function(url) {
        if (!url) return
        fetchImage(scope, elem, url)
      })
    }
  }
}])
<|endoftext|>


<commit_before>import { init } from '../helper/runtime'
import div from '../../../../render/vue/components/div'

import firstScreenAppearBundle from '../data/build/dotvue/first-screen-appear.js'

init('utils component', (Vue, helper) => {
  const spys = {
    'appear': sinon.spy(),
    'disappear': sinon.spy()
  }
  window._spy_first_screen_appear = function (evt) {
    const spy = spys[evt.type]
    spy && spy(evt)
  }

  const id = 'test-first-screen-appear'

  before(() => {
    helper.register('div', div)
  })

  describe('watchAppear', function () {
    it('should work when mounted and updated.', function (done) {
      helper.createVm(firstScreenAppearBundle, id)
      helper.registerDone(id, () => {
        const { appear: appearSpy, disappear: disappearSpy } = spys
        expect(appearSpy.callCount).to.equal(2)
        expect(disappearSpy.callCount).to.equal(1)
        expect(appearSpy.args[0][0].type).to.equal('appear')
        expect(appearSpy.args[1][0].type).to.equal('appear')
        expect(disappearSpy.args[0][0].type).to.equal('disappear')
        expect(appearSpy.args[0][0].direction).to.not.exist
        expect(appearSpy.args[1][0].direction).to.not.exist
        expect(disappearSpy.args[0][0].direction).to.not.exist
        helper.unregisterDone(id)
        done()
      })
    })
  })
})
<commit_msg>* [html5] improve component unit test case<commit_after>import { init } from '../helper/runtime'
import div from '../../../../render/vue/components/div'

import firstScreenAppearBundle from '../data/build/dotvue/first-screen-appear.js'
import * as components from '../../../../render/vue/utils/component'

init('utils component', (Vue, helper) => {
  const spys = {
    'appear': sinon.spy(),
    'disappear': sinon.spy()
  }
  window._spy_first_screen_appear = function (evt) {
    const spy = spys[evt.type]
    spy && spy(evt)
  }

  const id = 'test-first-screen-appear'
  let vm = null

  before(() => {
    helper.register('div', div)
    vm =helper.createVm(firstScreenAppearBundle, id)
  })

  describe('component functions', () => {
    it('getParentScroller', () => {
      const { getParentScroller } = components
      expect(getParentScroller).to.be.a('function')
      expect(getParentScroller(vm)).to.be.a('undefined')
    })
    it('isComponentVisible', () => {
      const { isComponentVisible } = components
      expect(isComponentVisible).to.be.a('function')
      expect(isComponentVisible(vm)).to.be.true
    })
  })
  describe('watchAppear', () => {
    it('should work when mounted and updated.', function (done) {
      helper.registerDone(id, () => {
        const { appear: appearSpy, disappear: disappearSpy } = spys
        expect(appearSpy.callCount).to.equal(2)
        expect(disappearSpy.callCount).to.equal(1)
        expect(appearSpy.args[0][0].type).to.equal('appear')
        expect(appearSpy.args[1][0].type).to.equal('appear')
        expect(disappearSpy.args[0][0].type).to.equal('disappear')
        expect(appearSpy.args[0][0].direction).to.not.exist
        expect(appearSpy.args[1][0].direction).to.not.exist
        expect(disappearSpy.args[0][0].direction).to.not.exist
        window.dispatchEvent(new Event('scroll'))
        setTimeout(() => {
          helper.unregisterDone(id)
          done()
        },25)
      })
    })
  })
})
<|endoftext|>


<commit_before>(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
  "use strict";

  var HINT_ELEMENT_CLASS        = "CodeMirror-hint";
  var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";

  CodeMirror.showHint = function(cm, getHints, options) {
    // We want a single cursor position.
    if (cm.listSelections().length > 1 || cm.somethingSelected()) return;
    if (getHints == null) {
      if (options && options.async) return;
      else getHints = CodeMirror.hint.auto;
    }

    if (cm.state.completionActive) cm.state.completionActive.close();

    var completion = cm.state.completionActive = new Completion(cm, getHints, options || {});
    CodeMirror.signal(cm, "startCompletion", cm);
    if (completion.options.async)
      getHints(cm, function(hints) { completion.showHints(hints); }, completion.options);
    else
      return completion.showHints(getHints(cm, completion.options));
  };

  function Completion(cm, getHints, options) {
    this.cm = cm;
    this.getHints = getHints;
    this.options = options;
    this.widget = this.onClose = null;
  }

  Completion.prototype = {
    close: function() {
      if (!this.active()) return;
      this.cm.state.completionActive = null;

      if (this.widget) this.widget.close();
      if (this.onClose) this.onClose();
      CodeMirror.signal(this.cm, "endCompletion", this.cm);
    },

    active: function() {
      return this.cm.state.completionActive == this;
    },

    pick: function(data, i) {
      var completion = data.list[i];
      if (completion.hint) completion.hint(this.cm, data, completion);
      else this.cm.replaceRange(getText(completion), completion.from||data.from, completion.to||data.to);
      CodeMirror.signal(data, "pick", completion);
      this.close();
    },

    showHints: function(data) {
      if (!data || !data.list.length || !this.active()) return this.close();

      if (this.options.completeSingle != false && data.list.length == 1)
        this.pick(data, 0);
      else
        this.showWidget(data);
    },

    showWidget: function(data) {
      this.widget = new Widget(this, data);
      CodeMirror.signal(data, "shown");

      var debounce = 0, completion = this, finished;
      var closeOn = this.options.closeCharacters || /[\s()\[\]{};:>,]/;
      var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length;

      var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
        return setTimeout(fn, 1000/60);
      };
      var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;

      function done() {
        if (finished) return;
        finished = true;
        completion.close();
        completion.cm.off("cursorActivity", activity);
        if (data) CodeMirror.signal(data, "close");
      }

      function update() {
        if (finished) return;
        CodeMirror.signal(data, "update");
        if (completion.options.async)
          completion.getHints(completion.cm, finishUpdate, completion.options);
        else
          finishUpdate(completion.getHints(completion.cm, completion.options));
      }
      function finishUpdate(data_) {
        data = data_;
        if (finished) return;
        if (!data || !data.list.length) return done();
        if (completion.widget) completion.widget.close();
        completion.widget = new Widget(completion, data);
      }

      function clearDebounce() {
        if (debounce) {
          cancelAnimationFrame(debounce);
          debounce = 0;
        }
      }

      function activity() {
        clearDebounce();
        var pos = completion.cm.getCursor(), line = completion.cm.getLine(pos.line);
        if (pos.line != startPos.line || line.length - pos.ch != startLen - startPos.ch ||
            pos.ch < startPos.ch || completion.cm.somethingSelected() ||
            (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) {
          completion.close();
        } else {
          debounce = requestAnimationFrame(update);
          if (completion.widget) completion.widget.close();
        }
      }
      this.cm.on("cursorActivity", activity);
      this.onClose = done;
    }
  };

  function getText(completion) {
    if (typeof completion == "string") return completion;
    else return completion.text;
  }

  function buildKeyMap(options, handle) {
    var baseMap = {
      Up: function() {handle.moveFocus(-1);},
      Down: function() {handle.moveFocus(1);},
      PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
      PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
      Home: function() {handle.setFocus(0);},
      End: function() {handle.setFocus(handle.length - 1);},
      Enter: handle.pick,
      Tab: handle.pick,
      Esc: handle.close
    };
    var ourMap = options.customKeys ? {} : baseMap;
    function addBinding(key, val) {
      var bound;
      if (typeof val != "string")
        bound = function(cm) { return val(cm, handle); };
      // This mechanism is deprecated
      else if (baseMap.hasOwnProperty(val))
        bound = baseMap[val];
      else
        bound = val;
      ourMap[key] = bound;
    }
    if (options.customKeys)
      for (var key in options.customKeys) if (options.customKeys.hasOwnProperty(key))
        addBinding(key, options.customKeys[key]);
    if (options.extraKeys)
      for (var key in options.extraKeys) if (options.extraKeys.hasOwnProperty(key))
        addBinding(key, options.extraKeys[key]);
    return ourMap;
  }

  function getHintElement(hintsElement, el) {
    while (el && el != hintsElement) {
      if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
      el = el.parentNode;
    }
  }

  function Widget(completion, data) {
    this.completion = completion;
    this.data = data;
    var widget = this, cm = completion.cm, options = completion.options;

    var hints = this.hints = document.createElement("ul");
    hints.className = "CodeMirror-hints";
    this.selectedHint = options.getDefaultSelection ? options.getDefaultSelection(cm,options,data) : 0;

    var completions = data.list;
    for (var i = 0; i < completions.length; ++i) {
      var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
      var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
      if (cur.className != null) className = cur.className + " " + className;
      elt.className = className;
      if (cur.render) cur.render(elt, data, cur);
      else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
      elt.hintId = i;
    }

    var pos = cm.cursorCoords(options.alignWithWord !== false ? data.from : null);
    var left = pos.left, top = pos.bottom, below = true;
    hints.style.left = left + "px";
    hints.style.top = top + "px";
    // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
    var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
    var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
    (options.container || document.body).appendChild(hints);
    var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
    if (overlapY > 0) {
      var height = box.bottom - box.top, curTop = box.top - (pos.bottom - pos.top);
      if (curTop - height > 0) { // Fits above cursor
        hints.style.top = (top = curTop - height) + "px";
        below = false;
      } else if (height > winH) {
        hints.style.height = (winH - 5) + "px";
        hints.style.top = (top = pos.bottom - box.top) + "px";
        var cursor = cm.getCursor();
        if (data.from.ch != cursor.ch) {
          pos = cm.cursorCoords(cursor);
          hints.style.left = (left = pos.left) + "px";
          box = hints.getBoundingClientRect();
        }
      }
    }
    var overlapX = box.left - winW;
    if (overlapX > 0) {
      if (box.right - box.left > winW) {
        hints.style.width = (winW - 5) + "px";
        overlapX -= (box.right - box.left) - winW;
      }
      hints.style.left = (left = pos.left - overlapX) + "px";
    }

    cm.addKeyMap(this.keyMap = buildKeyMap(options, {
      moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
      setFocus: function(n) { widget.changeActive(n); },
      menuSize: function() { return widget.screenAmount(); },
      length: completions.length,
      close: function() { completion.close(); },
      pick: function() { widget.pick(); },
      data: data
    }));

    if (options.closeOnUnfocus !== false) {
      var closingOnBlur;
      cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
      cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
    }

    var startScroll = cm.getScrollInfo();
    cm.on("scroll", this.onScroll = function() {
      var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
      var newTop = top + startScroll.top - curScroll.top;
      var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
      if (!below) point += hints.offsetHeight;
      if (point <= editor.top || point >= editor.bottom) return completion.close();
      hints.style.top = newTop + "px";
      hints.style.left = (left + startScroll.left - curScroll.left) + "px";
    });

    CodeMirror.on(hints, "dblclick", function(e) {
      var t = getHintElement(hints, e.target || e.srcElement);
      if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
    });

    CodeMirror.on(hints, "click", function(e) {
      var t = getHintElement(hints, e.target || e.srcElement);
      if (t && t.hintId != null) {
        widget.changeActive(t.hintId);
        if (options.completeOnSingleClick) widget.pick();
      }
    });

    CodeMirror.on(hints, "mousedown", function() {
      setTimeout(function(){cm.focus();}, 20);
    });

    CodeMirror.signal(data, "select", completions[0], hints.firstChild);
    return true;
  }

  Widget.prototype = {
    close: function() {
      if (this.completion.widget != this) return;
      this.completion.widget = null;
      this.hints.parentNode.removeChild(this.hints);
      this.completion.cm.removeKeyMap(this.keyMap);

      var cm = this.completion.cm;
      if (this.completion.options.closeOnUnfocus !== false) {
        cm.off("blur", this.onBlur);
        cm.off("focus", this.onFocus);
      }
      cm.off("scroll", this.onScroll);
    },

    pick: function() {
      this.completion.pick(this.data, this.selectedHint);
    },

    changeActive: function(i, avoidWrap) {
      if (i >= this.data.list.length)
        i = avoidWrap ? this.data.list.length - 1 : 0;
      else if (i < 0)
        i = avoidWrap ? 0  : this.data.list.length - 1;
      if (this.selectedHint == i) return;
      var node = this.hints.childNodes[this.selectedHint];
      node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
      node = this.hints.childNodes[this.selectedHint = i];
      node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
      if (node.offsetTop < this.hints.scrollTop)
        this.hints.scrollTop = node.offsetTop - 3;
      else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
        this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
      CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
    },

    screenAmount: function() {
      return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
    }
  };

  CodeMirror.registerHelper("hint", "auto", function(cm, options) {
    var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
    if (helpers.length) {
      for (var i = 0; i < helpers.length; i++) {
        var cur = helpers[i](cm, options);
        if (cur && cur.list.length) return cur;
      }
    } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
      if (words) return CodeMirror.hint.fromList(cm, {words: words});
    } else if (CodeMirror.hint.anyword) {
      return CodeMirror.hint.anyword(cm, options);
    }
  });

  CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
    var cur = cm.getCursor(), token = cm.getTokenAt(cur);
    var found = [];
    for (var i = 0; i < options.words.length; i++) {
      var word = options.words[i];
      if (word.slice(0, token.string.length) == token.string)
        found.push(word);
    }

    if (found.length) return {
      list: found,
      from: CodeMirror.Pos(cur.line, token.start),
            to: CodeMirror.Pos(cur.line, token.end)
    };
  });

  CodeMirror.commands.autocomplete = CodeMirror.showHint;
});
<commit_msg>[show-hint addon] Add an origin string to completion changes<commit_after>(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
  "use strict";

  var HINT_ELEMENT_CLASS        = "CodeMirror-hint";
  var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";

  CodeMirror.showHint = function(cm, getHints, options) {
    // We want a single cursor position.
    if (cm.listSelections().length > 1 || cm.somethingSelected()) return;
    if (getHints == null) {
      if (options && options.async) return;
      else getHints = CodeMirror.hint.auto;
    }

    if (cm.state.completionActive) cm.state.completionActive.close();

    var completion = cm.state.completionActive = new Completion(cm, getHints, options || {});
    CodeMirror.signal(cm, "startCompletion", cm);
    if (completion.options.async)
      getHints(cm, function(hints) { completion.showHints(hints); }, completion.options);
    else
      return completion.showHints(getHints(cm, completion.options));
  };

  function Completion(cm, getHints, options) {
    this.cm = cm;
    this.getHints = getHints;
    this.options = options;
    this.widget = this.onClose = null;
  }

  Completion.prototype = {
    close: function() {
      if (!this.active()) return;
      this.cm.state.completionActive = null;

      if (this.widget) this.widget.close();
      if (this.onClose) this.onClose();
      CodeMirror.signal(this.cm, "endCompletion", this.cm);
    },

    active: function() {
      return this.cm.state.completionActive == this;
    },

    pick: function(data, i) {
      var completion = data.list[i];
      if (completion.hint) completion.hint(this.cm, data, completion);
      else this.cm.replaceRange(getText(completion), completion.from || data.from,
                                completion.to || data.to, "complete");
      CodeMirror.signal(data, "pick", completion);
      this.close();
    },

    showHints: function(data) {
      if (!data || !data.list.length || !this.active()) return this.close();

      if (this.options.completeSingle != false && data.list.length == 1)
        this.pick(data, 0);
      else
        this.showWidget(data);
    },

    showWidget: function(data) {
      this.widget = new Widget(this, data);
      CodeMirror.signal(data, "shown");

      var debounce = 0, completion = this, finished;
      var closeOn = this.options.closeCharacters || /[\s()\[\]{};:>,]/;
      var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length;

      var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
        return setTimeout(fn, 1000/60);
      };
      var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;

      function done() {
        if (finished) return;
        finished = true;
        completion.close();
        completion.cm.off("cursorActivity", activity);
        if (data) CodeMirror.signal(data, "close");
      }

      function update() {
        if (finished) return;
        CodeMirror.signal(data, "update");
        if (completion.options.async)
          completion.getHints(completion.cm, finishUpdate, completion.options);
        else
          finishUpdate(completion.getHints(completion.cm, completion.options));
      }
      function finishUpdate(data_) {
        data = data_;
        if (finished) return;
        if (!data || !data.list.length) return done();
        if (completion.widget) completion.widget.close();
        completion.widget = new Widget(completion, data);
      }

      function clearDebounce() {
        if (debounce) {
          cancelAnimationFrame(debounce);
          debounce = 0;
        }
      }

      function activity() {
        clearDebounce();
        var pos = completion.cm.getCursor(), line = completion.cm.getLine(pos.line);
        if (pos.line != startPos.line || line.length - pos.ch != startLen - startPos.ch ||
            pos.ch < startPos.ch || completion.cm.somethingSelected() ||
            (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) {
          completion.close();
        } else {
          debounce = requestAnimationFrame(update);
          if (completion.widget) completion.widget.close();
        }
      }
      this.cm.on("cursorActivity", activity);
      this.onClose = done;
    }
  };

  function getText(completion) {
    if (typeof completion == "string") return completion;
    else return completion.text;
  }

  function buildKeyMap(options, handle) {
    var baseMap = {
      Up: function() {handle.moveFocus(-1);},
      Down: function() {handle.moveFocus(1);},
      PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
      PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
      Home: function() {handle.setFocus(0);},
      End: function() {handle.setFocus(handle.length - 1);},
      Enter: handle.pick,
      Tab: handle.pick,
      Esc: handle.close
    };
    var ourMap = options.customKeys ? {} : baseMap;
    function addBinding(key, val) {
      var bound;
      if (typeof val != "string")
        bound = function(cm) { return val(cm, handle); };
      // This mechanism is deprecated
      else if (baseMap.hasOwnProperty(val))
        bound = baseMap[val];
      else
        bound = val;
      ourMap[key] = bound;
    }
    if (options.customKeys)
      for (var key in options.customKeys) if (options.customKeys.hasOwnProperty(key))
        addBinding(key, options.customKeys[key]);
    if (options.extraKeys)
      for (var key in options.extraKeys) if (options.extraKeys.hasOwnProperty(key))
        addBinding(key, options.extraKeys[key]);
    return ourMap;
  }

  function getHintElement(hintsElement, el) {
    while (el && el != hintsElement) {
      if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
      el = el.parentNode;
    }
  }

  function Widget(completion, data) {
    this.completion = completion;
    this.data = data;
    var widget = this, cm = completion.cm, options = completion.options;

    var hints = this.hints = document.createElement("ul");
    hints.className = "CodeMirror-hints";
    this.selectedHint = options.getDefaultSelection ? options.getDefaultSelection(cm,options,data) : 0;

    var completions = data.list;
    for (var i = 0; i < completions.length; ++i) {
      var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
      var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
      if (cur.className != null) className = cur.className + " " + className;
      elt.className = className;
      if (cur.render) cur.render(elt, data, cur);
      else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
      elt.hintId = i;
    }

    var pos = cm.cursorCoords(options.alignWithWord !== false ? data.from : null);
    var left = pos.left, top = pos.bottom, below = true;
    hints.style.left = left + "px";
    hints.style.top = top + "px";
    // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
    var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
    var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
    (options.container || document.body).appendChild(hints);
    var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
    if (overlapY > 0) {
      var height = box.bottom - box.top, curTop = box.top - (pos.bottom - pos.top);
      if (curTop - height > 0) { // Fits above cursor
        hints.style.top = (top = curTop - height) + "px";
        below = false;
      } else if (height > winH) {
        hints.style.height = (winH - 5) + "px";
        hints.style.top = (top = pos.bottom - box.top) + "px";
        var cursor = cm.getCursor();
        if (data.from.ch != cursor.ch) {
          pos = cm.cursorCoords(cursor);
          hints.style.left = (left = pos.left) + "px";
          box = hints.getBoundingClientRect();
        }
      }
    }
    var overlapX = box.left - winW;
    if (overlapX > 0) {
      if (box.right - box.left > winW) {
        hints.style.width = (winW - 5) + "px";
        overlapX -= (box.right - box.left) - winW;
      }
      hints.style.left = (left = pos.left - overlapX) + "px";
    }

    cm.addKeyMap(this.keyMap = buildKeyMap(options, {
      moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
      setFocus: function(n) { widget.changeActive(n); },
      menuSize: function() { return widget.screenAmount(); },
      length: completions.length,
      close: function() { completion.close(); },
      pick: function() { widget.pick(); },
      data: data
    }));

    if (options.closeOnUnfocus !== false) {
      var closingOnBlur;
      cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
      cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
    }

    var startScroll = cm.getScrollInfo();
    cm.on("scroll", this.onScroll = function() {
      var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
      var newTop = top + startScroll.top - curScroll.top;
      var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
      if (!below) point += hints.offsetHeight;
      if (point <= editor.top || point >= editor.bottom) return completion.close();
      hints.style.top = newTop + "px";
      hints.style.left = (left + startScroll.left - curScroll.left) + "px";
    });

    CodeMirror.on(hints, "dblclick", function(e) {
      var t = getHintElement(hints, e.target || e.srcElement);
      if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
    });

    CodeMirror.on(hints, "click", function(e) {
      var t = getHintElement(hints, e.target || e.srcElement);
      if (t && t.hintId != null) {
        widget.changeActive(t.hintId);
        if (options.completeOnSingleClick) widget.pick();
      }
    });

    CodeMirror.on(hints, "mousedown", function() {
      setTimeout(function(){cm.focus();}, 20);
    });

    CodeMirror.signal(data, "select", completions[0], hints.firstChild);
    return true;
  }

  Widget.prototype = {
    close: function() {
      if (this.completion.widget != this) return;
      this.completion.widget = null;
      this.hints.parentNode.removeChild(this.hints);
      this.completion.cm.removeKeyMap(this.keyMap);

      var cm = this.completion.cm;
      if (this.completion.options.closeOnUnfocus !== false) {
        cm.off("blur", this.onBlur);
        cm.off("focus", this.onFocus);
      }
      cm.off("scroll", this.onScroll);
    },

    pick: function() {
      this.completion.pick(this.data, this.selectedHint);
    },

    changeActive: function(i, avoidWrap) {
      if (i >= this.data.list.length)
        i = avoidWrap ? this.data.list.length - 1 : 0;
      else if (i < 0)
        i = avoidWrap ? 0  : this.data.list.length - 1;
      if (this.selectedHint == i) return;
      var node = this.hints.childNodes[this.selectedHint];
      node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
      node = this.hints.childNodes[this.selectedHint = i];
      node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
      if (node.offsetTop < this.hints.scrollTop)
        this.hints.scrollTop = node.offsetTop - 3;
      else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
        this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
      CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
    },

    screenAmount: function() {
      return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
    }
  };

  CodeMirror.registerHelper("hint", "auto", function(cm, options) {
    var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
    if (helpers.length) {
      for (var i = 0; i < helpers.length; i++) {
        var cur = helpers[i](cm, options);
        if (cur && cur.list.length) return cur;
      }
    } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
      if (words) return CodeMirror.hint.fromList(cm, {words: words});
    } else if (CodeMirror.hint.anyword) {
      return CodeMirror.hint.anyword(cm, options);
    }
  });

  CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
    var cur = cm.getCursor(), token = cm.getTokenAt(cur);
    var found = [];
    for (var i = 0; i < options.words.length; i++) {
      var word = options.words[i];
      if (word.slice(0, token.string.length) == token.string)
        found.push(word);
    }

    if (found.length) return {
      list: found,
      from: CodeMirror.Pos(cur.line, token.start),
            to: CodeMirror.Pos(cur.line, token.end)
    };
  });

  CodeMirror.commands.autocomplete = CodeMirror.showHint;
});
<|endoftext|>


<commit_before>steal.config({
	map: {
		"*": {
			'jquery/jquery.js' : "jquery",
			"can/util/util.js": "can/util/jquery/jquery.js"
		}
	},
	paths: {
		"jquery": "can/util/jquery/jquery.1.8.2.js"
	},
	ext: {
		js: "js",
		css: "css",
		less: "steal/less/less.js",
		coffee: "steal/coffee/coffee.js",
		ejs: "can/view/ejs/ejs.js"
	}
})
<commit_msg>Updating stealconfig<commit_after>steal.config({
	map: {
		"*": {
			"jquery/jquery.js" : "jquery",
			"can/util/util.js": "can/util/jquery/jquery.js"
		}
	},
	paths: {
		"jquery": "can/lib/jquery.1.8.3.js",
		"mootools/mootools.js" : "can/lib/mootools-core-1.4.3.js",
		"dojo/dojo.js" : "can/util/dojo/dojo-1.8.1.js",
		"yui/yui.js" : "can/lib/yui-3.7.3.js",
		"zepto/zepto.js" : "can/lib/zepto.1.0rc1.js"
	},
	shim : {
		jquery: {
			exports: "jQuery"
		}
	},
	ext: {
		js: "js",
		css: "css",
		less: "steal/less/less.js",
		coffee: "steal/coffee/coffee.js",
		ejs: "can/view/ejs/ejs.js"
	}
})
<|endoftext|>


<commit_before>module.exports = {
  description: 'Add bower dependencies for bootstrap and bootswatch to the project',

  normalizeEntityName: function() {
    // allows us to run ember -g ember-cli-bootswatch and not blow up
    // because ember cli normally expects the format
    // ember generate <entitiyName> <blueprint>
  },

  afterInstall: function(options) {
    return this.addBowerPackagesToProject([
      {name: 'bootstrap', target: '^3.3.1'},
      {name: 'bootswatch', target: '^3.3.1'}
    ]);
  }

};
<commit_msg>Feature detect .addBowerPackagesToProject()<commit_after>module.exports = {
  description: 'Add bower dependencies for bootstrap and bootswatch to the project',

  normalizeEntityName: function() {
    // allows us to run ember -g ember-cli-bootswatch and not blow up
    // because ember cli normally expects the format
    // ember generate <entitiyName> <blueprint>
  },

  afterInstall: function(options) {
    var addon = this;

    // Ability to add multiple bower packages introduced in ember-cli 0.1.2
    if (this.addBowerPackagesToProject) {
      return this.addBowerPackagesToProject([
        {name: 'bootstrap', target: '^3.3.1'},
        {name: 'bootswatch', target: '^3.3.1'}
      ]);
    } else { // Else need to add them individually
      return this.addBowerPackageToProject('bootstrap', '^3.3.1').then(function(){
        return addon.addBowerPackageToProject('bootswatch', '^3.3.1');
      });
    }

  } // :afterInstall

};
<|endoftext|>


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

  // window.onload = function (){
  //   if(document.cookie.length != 0) {
  //     var cookie = document.cookie
  //     var cookieArray = cookie.split('=');
  //     saved_file_name = cookieArray[1].replace(/[; ]+/g, " ").trim();
  //     document.current_file = saved_file_name;
  //
  //     var title;
  //     if(cookieArray[1] === ''){
  //       title = "untitled.md"
  //       console.log('saved_file_name => ',saved_file_name)
  //     } else {
  //       title = saved_file_name
  //       console.log('works!!!')
  //     }
  //     $(".file_name").html(title);
  //
  //     console.log('loaded!!!')
  //   }
  // }

  console.log(marked('I am using __markdown__!'));

  $('.nav_add_file_btn').on('click', function(){
    console.log("click");
    title = prompt("Enter filename") + ".md";
    console.log(title);

    $(".file_name").html(title);

  })

  $("#sv_file").on('click', function(){
    var title = $(".file_name").html();
    var newTitle = title.substring(0, title.length - 3)
    var info = $(".markdown_textarea").val();
    console.log(info);
    var url = '/saveZone'
    var data = {
      data: info,
      file: title
    }
    fetch(url, {
      method: 'post',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
      body: JSON.stringify(data)
    })
    .then(() => {
      document.cookie = "fileName=" + title + "; expires=Fri, 31 Dec 9999 23:59:59 GMT"

      var cookieString = document.cookie
      console.log('cookieString => ',cookieString); // why do we see NewerThing.md ?
      console.log('you\'ve been saved');

    })
    .catch(function(e) {
      console.log('EEEEEEERROR!' + e);
    })
    console.log("heyoo!");

    if (title !== document.getElementById('file_titles').firstChild.innerHTML) {
      console.log("this should work");
      $('.nav_link_container').prepend('
'+title+'
')
    }
  })

  $(document).on('click', '.fa-trash-o',
  function () {
      var id = $(this).data('id')
      if(id.substring(id.length - 3, id.length) === '.md'){
        id = id.substring(0, id.length - 3);
      }
      let url = routeSlice(id)
      console.log(url);
      fetch(url, {
        method: 'delete'
      }).then(() => {
        $('.name-'+id).remove()
        $('.delete-'+id).remove()
      })
    }
  )

  $( '.markdown_textarea' ).keyup(function() {
    var value = marked( $( this ).val() );
    $( '.preview_textarea' ).html( value );
  }).keyup();

  $(document).on('click', '.nav_link',
  function(){
    var url = $(this).data('id')
    $('.file_name').html(url + '.md')
    url = '/'+url+'.md'
    console.log('url --> ',url)

    fetch(url, {
      method: 'get'
    }).then(
      function(res){
        console.log('res => ',res)
        return res.text()
      }
    ).then(
      function(content){
        console.log('content => ', content)
        $('.markdown_textarea').val(content);
        $('.preview_textarea').html(marked(content));
      }
    )
    var title = $(this).data('id')+'.md'
    console.log('title ===> ',title);
    document.cookie = "fileName=" + title + "; expires=Fri, 31 Dec 9999 23:59:59 GMT"
    console.log('document.cookie => ',document.cookie);
  }

)

});

function routeSlice(filename) {
  console.log(filename.substring(filename.length - 3,filename.length - 1));
  if (filename.substring(filename.length - 3,filename.length) === '.md') {
    return '/' + filename
  } else {
  return '/' + filename + '.md'
  }
}
<commit_msg>patch fix fix<commit_after>$(document).ready(function(){

  window.onload = function (){
    if(document.cookie.length != 0) {
      var cookie = document.cookie
      var cookieArray = cookie.split('=');
      saved_file_name = cookieArray[1].replace(/[; ]+/g, " ").trim();
      document.current_file = saved_file_name;

      var title;
      if(cookieArray[1] === ''){
        title = "untitled.md"
        console.log('saved_file_name => ',saved_file_name)
      } else {
        title = saved_file_name
        console.log('works!!!')
      }
      $(".file_name").html(title);

      console.log('loaded!!!')
    }
  }

  console.log(marked('I am using __markdown__!'));

  $('.nav_add_file_btn').on('click', function(){
    console.log("click");
    title = prompt("Enter filename") + ".md";
    console.log(title);

    $(".file_name").html(title);

  })

  $("#sv_file").on('click', function(){
    var title = $(".file_name").html();
    var newTitle = title.substring(0, title.length - 3)
    var info = $(".markdown_textarea").val();
    console.log(info);
    var url = '/saveZone'
    var data = {
      data: info,
      file: title
    }
    fetch(url, {
      method: 'post',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
      body: JSON.stringify(data)
    })
    .then(() => {
      document.cookie = "fileName=" + title + "; expires=Fri, 31 Dec 9999 23:59:59 GMT"

      var cookieString = document.cookie
      console.log('cookieString => ',cookieString); // why do we see NewerThing.md ?
      console.log('you\'ve been saved');

    })
    .catch(function(e) {
      console.log('EEEEEEERROR!' + e);
    })
    console.log("heyoo!");

    if (title !== document.getElementById('file_titles').firstChild.innerHTML) {
      console.log("this should work");
      $('.nav_link_container').prepend('
'+title+'
')
    }
  })

  $(document).on('click', '.fa-trash-o',
  function () {
      var id = $(this).data('id')
      if(id.substring(id.length - 3, id.length) === '.md'){
        id = id.substring(0, id.length - 3);
      }
      let url = routeSlice(id)
      console.log(url);
      fetch(url, {
        method: 'delete'
      }).then(() => {
        $('.name-'+id).remove()
        $('.delete-'+id).remove()
      })
    }
  )

  $( '.markdown_textarea' ).keyup(function() {
    var value = marked( $( this ).val() );
    $( '.preview_textarea' ).html( value );
  }).keyup();

  $(document).on('click', '.nav_link',
  function(){
    var url = $(this).data('id')
    $('.file_name').html(url + '.md')
    url = '/'+url+'.md'
    console.log('url --> ',url)

    fetch(url, {
      method: 'get'
    }).then(
      function(res){
        console.log('res => ',res)
        return res.text()
      }
    ).then(
      function(content){
        console.log('content => ', content)
        $('.markdown_textarea').val(content);
        $('.preview_textarea').html(marked(content));
      }
    )
    var title = $(this).data('id')+'.md'
    console.log('title ===> ',title);
    document.cookie = "fileName=" + title + "; expires=Fri, 31 Dec 9999 23:59:59 GMT"
    console.log('document.cookie => ',document.cookie);
  }

)

});

function routeSlice(filename) {
  console.log(filename.substring(filename.length - 3,filename.length - 1));
  if (filename.substring(filename.length - 3,filename.length) === '.md') {
    return '/' + filename
  } else {
  return '/' + filename + '.md'
  }
}
<|endoftext|>