{"action":"created","comment":{"url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/comments/32111010","id":32111010,"diff_hunk":"@@ -1,710 +1,7 @@\n 'use strict';\n-/* global Normalizer */\n-/* global SimplePhoneMatcher */\n+/* global MatcherObj */\n \n-var contacts = window.contacts || {};\n \n-contacts.MatcherObj = function(pdataProvider) {\n-  var blankRegExp = /\\s+/g;\n-\n-  var FB_CATEGORY = 'facebook';\n-  var FB_LINKED = 'fb_linked';\n-  \n-  var dataProvider = pdataProvider;\n-\n-  // Multiple matcher Object. It tries to find a set of Contacts that match at\n-  // least one of the targets passed as parameters\n-  // ptargets: They are the targets (telephone numbers, emails) we want to find\n-  // in the Contacts DB\n-  // mmatchingOptions: are the matching options to be passed to the mozContacts\n-  // API. Particularly filterBy and filterOp\n-  // MultipleMatcher.onmatch is called when there is at least one matching\n-  // MultipleMatcher.onmismatch is called when there are no matches\n-  function MultipleMatcher(ptargets, pmatchingOptions) {\n-    var next = 0;\n-    var self = this;\n-    var targets = ptargets;\n-    var matchingOptions = pmatchingOptions;\n-    var finalMatchings = {};\n-\n-    function doMatchBy(target, callbacks) {\n-      var options = {\n-        filterValue: target,\n-        filterBy: matchingOptions.filterBy,\n-        filterOp: matchingOptions.filterOp\n-      };\n-\n-      var req = dataProvider.find(options);\n-\n-      req.onsuccess = function() {\n-        var matchings = req.result.filter(function(aResult) {\n-          return filterFacebook(aResult, matchingOptions.linkParams);\n-        });\n-\n-        var filterBy = options.filterBy;\n-\n-        var sanitizedTarget = SimplePhoneMatcher.sanitizedNumber(target);\n-        var targetVariants = SimplePhoneMatcher.generateVariants(target);\n-\n-        matchings.forEach(function(aMatching) {\n-          if (matchingOptions.selfContactId === aMatching.id) {\n-            return;\n-          }\n-\n-          var field = options.filterBy[0];\n-          var values = aMatching[field];\n-\n-          values.forEach(function(aValue) {\n-            var value = aValue.value;\n-            var sanitizedValue = SimplePhoneMatcher.sanitizedNumber(value);\n-            var valueMatched = false;\n-\n-            if (targetVariants.length > 1) {\n-              if (targetVariants.indexOf(sanitizedValue) !== -1) {\n-                valueMatched = true;\n-              }\n-            } else if (SimplePhoneMatcher.generateVariants(sanitizedValue).\n-                       indexOf(sanitizedTarget) !== -1) {\n-                valueMatched = true;\n-              }\n-\n-            if (valueMatched) {\n-              var matchings, matchingObj;\n-              if (!finalMatchings[aMatching.id]) {\n-                matchingObj = {\n-                  matchings: {},\n-                  matchingContact: aMatching\n-                };\n-                finalMatchings[aMatching.id] = matchingObj;\n-                matchingObj.matchings[filterBy[0]] = [];\n-              }\n-              else {\n-                matchingObj = finalMatchings[aMatching.id];\n-              }\n-              matchings = matchingObj.matchings[filterBy[0]];\n-\n-              // Avoinding to report multiple matchings due to variants\n-              var sameValueMatchings = matchings.filter(function(matching) {\n-                return (matching.matchedValue === value);\n-              });\n-              if (sameValueMatchings.length === 0) {\n-                matchings.push({\n-                  'target': target,\n-                  'matchedValue': value\n-                });\n-              }\n-            }\n-          });\n-        });  // matchings.forEach\n-\n-        carryOn();\n-      }; // onsuccess\n-\n-      req.onerror = function(e) {\n-        window.console.error('Error while trying to do the matching',\n-                             e.target.error.name);\n-        notifyMismatch(self);\n-      };\n-    }\n-\n-    function carryOn() {\n-      next++;\n-      if (next < targets.length) {\n-        doMatchBy(targets[next], callbacks);\n-      }\n-      else if (Object.keys(finalMatchings).length > 0) {\n-        notifyMatch(self, finalMatchings);\n-      }\n-      else {\n-        notifyMismatch(self);\n-      }\n-    }\n-\n-    function matched(contacts) {\n-      carryOn();\n-    }\n-\n-    var callbacks = {\n-      onmatch: matched,\n-      onmismatch: carryOn\n-    };\n-\n-    this.start = function() {\n-      doMatchBy(targets[0], callbacks);\n-    };\n-  }\n-\n-  function notifyMatch(obj, matchings) {\n-    typeof obj.onmatch === 'function' && obj.onmatch(matchings);\n-  }\n-\n-  function notifyMismatch(obj) {\n-    typeof obj.onmismatch === 'function' && obj.onmismatch();\n-  }\n-\n-  // Match a Contact by the field and filter specified as parameters\n-  // Callbacks is an object that declares onmatch and onmismatch callbacks\n-  function matchBy(aContact, field, filterOper, callbacks, poptions) {\n-    var options = poptions || {};\n-    var values = [];\n-\n-    if (Array.isArray(aContact[field])) {\n-      aContact[field].forEach(function(aField) {\n-        if (field === 'tel') {\n-          // Bug 924378 Contacts API does not properly match phone numbers\n-          // and their variants\n-          var variants = SimplePhoneMatcher.generateVariants(aField.value);\n-          variants.forEach(function(aVariant) {\n-            values.push(aVariant);\n-          });\n-        }\n-        else if (typeof aField.value === 'string') {\n-          values.push(aField.value.trim());\n-        }\n-      });\n-    }\n-\n-    if (values.length > 0) {\n-      var matcher = new MultipleMatcher(values, {\n-        filterBy: [field],\n-        filterOp: filterOper,\n-        selfContactId: aContact.id,\n-        linkParams: options\n-      });\n-      matcher.onmatch = callbacks.onmatch;\n-\n-      matcher.onmismatch = callbacks.onmismatch;\n-\n-      matcher.start();\n-    }\n-    else {\n-      notifyMismatch(callbacks);\n-    }\n-  }\n-\n-  function matchByTel(aContact, callbacks, options) {\n-    matchBy(aContact, 'tel', 'match', callbacks, options);\n-  }\n-\n-  function matchByEmail(aContact, callbacks, options) {\n-    matchBy(aContact, 'email', 'startsWith', callbacks, options);\n-  }\n-\n-  // Performs a matching for an incoming contact 'aContact' and the mode\n-  // ['active', passive'] specified as parameter. Callbacks is an object\n-  // with the onmatch and onmismatch functions that will be called after\n-  // the matching process has finished\n-  function doMatch(aContact, mode, callbacks) {\n-    if (mode === 'passive') {\n-      doMatchPassive(aContact, callbacks);\n-    }\n-    else if (mode === 'active') {\n-      doMatchActive(aContact, callbacks);\n-    }\n-  }\n-\n-  // Implements the active mode 'matching'\n-  function doMatchTelAndEmail(aContact, callbacks, options) {\n-    var localCbs = {\n-      onmatch: function(telMatches) {\n-        var matchCbs = {\n-          onmatch: function(mailMatches) {\n-            // Have a unique set of matches\n-            var allMatches = telMatches;\n-            Object.keys(mailMatches).forEach(function(aMatch) {\n-              if (!allMatches[aMatch]) {\n-                allMatches[aMatch] = mailMatches[aMatch];\n-              }\n-              else {\n-                allMatches[aMatch].matchings.email =\n-                                        mailMatches[aMatch].matchings.email;\n-              }\n-            });\n-            notifyMatch(callbacks, allMatches);\n-          },\n-          onmismatch: function() {\n-            notifyMatch(callbacks, telMatches);\n-          }\n-        };\n-        matchByEmail(aContact, matchCbs, options);\n-      },\n-      onmismatch: function() {\n-        matchByEmail(aContact, callbacks, options);\n-      }\n-    };\n-    matchByTel(aContact, localCbs, options);\n-  }\n-\n-  function getSourceSimUrl(aContact) {\n-    var out;\n-\n-    if (Array.isArray(aContact.category) &&\n-      aContact.category.indexOf('sim') !== -1 && Array.isArray(aContact.url)) {\n-      for (var j = 0; j < aContact.url.length; j++) {\n-        var aUrl = aContact.url[j];\n-        if (aUrl.type.indexOf('source') !== -1 &&\n-                                          aUrl.type.indexOf('sim') !== -1) {\n-          out = aUrl.value;\n-        }\n-      }\n-    }\n-\n-    return out;\n-  }\n-\n-  // Implements the silent mode matching\n-  function doMatchPassive(aContact, callbacks) {\n-    if (!hasName(aContact)) {\n-      notifyMismatch(callbacks);\n-      return;\n-    }\n-\n-    // SIM Contacts are detected and matched accordingly\n-    // We try to match by name and then try to see if they correspond to\n-    // the same SIM contact. In that case matching is reported\n-    var simUrl = getSourceSimUrl(aContact);\n-    if (simUrl) {\n-      performPassiveMatchForSimContact(simUrl, aContact, callbacks);\n-    }\n-    else {\n-      performPassiveMatch(aContact, callbacks);\n-    }\n-  }\n-\n-  function performPassiveMatchForSimContact(simUrl, aContact, callbacks) {\n-    var localCbs = {\n-      onmatch: function(results) {\n-        var matchingFound = false;\n-\n-        Object.keys(results).forEach(function(aResultId) {\n-          var matchingContact = results[aResultId].matchingContact;\n-          var matchingUrl = getSourceSimUrl(matchingContact);\n-          if (matchingUrl && matchingUrl === simUrl) {\n-            matchingFound = true;\n-            var matchings = {};\n-            matchings[aResultId] = {\n-              matchingContact: matchingContact\n-            };\n-            callbacks.onmatch(matchings);\n-            return;\n-          }\n-        });\n-        if (!matchingFound) {\n-          performPassiveMatch(aContact, callbacks);\n-        }\n-      },\n-      onmismatch: function() {\n-        performPassiveMatch(aContact, callbacks);\n-      }\n-    };\n-    // Matching by name and then see if they have the same SIM contact source\n-    matchByName(aContact, localCbs);\n-  }\n-\n-  // Implements the silent mode matching\n-  function performPassiveMatch(aContact, callbacks) {\n-    var matchingsFound = {};\n-\n-    var localCbs = {\n-      onmatch: function(results) {\n-        // Results will contain contacts that match by tel or email\n-        // Normalizing the strings\n-        var names = [];\n-        Object.keys(results).forEach(function(aResultId) {\n-          var mContact = results[aResultId].matchingContact;\n-\n-          if (!hasName(mContact)) {\n-            return;\n-          }\n-\n-          // As the number of candidates here will be short a normal search\n-          // will be conducted\n-          var targetFN = null;\n-          if (!isEmptyStr(aContact.familyName)) {\n-            targetFN = Normalizer.toAscii(\n-                          aContact.familyName[0].trim().toLowerCase()).\n-                          replace(blankRegExp, '');\n-          }\n-          var targetGN = null;\n-          if (!isEmptyStr(aContact.givenName)) {\n-            targetGN = Normalizer.toAscii(\n-                          aContact.givenName[0].trim().toLowerCase()).\n-                          replace(blankRegExp, '');\n-          }\n-          // To support seamless matching of SIM contacts\n-          var targetName = (targetGN || '') + (targetFN || '');\n-\n-          var mFamilyName = null;\n-          var mGivenName = null;\n-\n-          if (!isEmptyStr(mContact.familyName)) {\n-            mFamilyName = Normalizer.toAscii(\n-                                  mContact.familyName[0].trim().toLowerCase()).\n-                          replace(blankRegExp, '');\n-          }\n-\n-          if (!isEmptyStr(mContact.givenName)) {\n-            mGivenName = Normalizer.toAscii(\n-                                    mContact.givenName[0].trim().toLowerCase()).\n-                          replace(blankRegExp, '');\n-          }\n-          // To support seamless matching of SIM contacts\n-          var mName = (mGivenName || '') + (mFamilyName || '');\n-\n-          names.push({\n-            contact: mContact,\n-            familyName: mFamilyName,\n-            givenName: mGivenName,\n-            name: mName\n-          });\n-\n-          var matchingList = names.filter(function(obj) {\n-            return ((obj.familyName === targetFN &&\n-                     obj.givenName === targetGN) ||\n-                    (obj.name && obj.name === targetName) && (\n-                    !Array.isArray(obj.contact.category) ||\n-                    obj.contact.category.indexOf(FB_CATEGORY) === -1));\n-          });\n-\n-          matchingList.forEach(function(aMatching) {\n-            var contact = aMatching.contact;\n-            matchingsFound[contact.id] = {\n-              matchingContact: contact\n-            };\n-          });\n-        });\n-\n-        reconcileResults(aContact, matchingsFound, results, callbacks);\n-      },\n-\n-      onmismatch: function() {\n-        notifyMismatch(callbacks);\n-      }\n-    };\n-\n-    // Matching by email and phone number, then match by names\n-    doMatchTelAndEmail(aContact, localCbs);\n-  }\n-\n-  function doMatchActive(aContact, callbacks) {\n-    var options = {\n-      linkedMatched: {},\n-      linkedTo: getLinkedTo(aContact)\n-    };\n-\n-    var localCbs = {\n-      onmatch: function(results) {\n-        var cbsName = {\n-          onmatch: function(nameResults) {\n-            Object.keys(nameResults).forEach(function(aId) {\n-              if (!results[aId]) {\n-                results[aId] = nameResults[aId];\n-              }\n-              else {\n-                results[aId].matchings.name =\n-                                          nameResults[aId].matchings.name;\n-              }\n-            });\n-\n-            notifyMatch(callbacks, results);\n-          },\n-          onmismatch: function() {\n-            notifyMatch(callbacks, results);\n-          }\n-        };\n-\n-        matchByName(aContact, cbsName, options);\n-      },\n-      onmismatch: function() {\n-        matchByName(aContact, callbacks, options);\n-      }\n-    };\n-\n-    // Matching by email and phone number, then match by names\n-    doMatchTelAndEmail(aContact, localCbs, options);\n-  }\n-\n-  function notifyFindNameReady() {\n-    document.dispatchEvent(new CustomEvent('by_name_ready'));\n-  }\n-\n-  // Used for the active mode. Performs matching by familyName and givenName\n-  function matchByName(aContact, callbacks, options) {\n-    // First we try to find by familyName\n-    // Afterwards we search by givenName\n-    var isSimContact = (Array.isArray(aContact.category) &&\n-                        aContact.category.indexOf('sim') !== -1);\n-\n-    if ((isEmptyStr(aContact.familyName) || isEmptyStr(aContact.givenName)) &&\n-       !isSimContact) {\n-      notifyMismatch(callbacks);\n-      return;\n-    }\n-\n-    var finalResult = {};\n-\n-    var resultsByName = [];\n-    if (!isEmptyStr(aContact.name)) {\n-      var targetName = aContact.name[0].trim();\n-      // Filter by familyName using startsWith. Gecko 'startsWith' operation\n-      // acts as 'equal' but does not match case.\n-      var reqName = dataProvider.find({\n-        filterValue: targetName,\n-        filterBy: ['name'],\n-        filterOp: 'startsWith'\n-      });\n-\n-      reqName.onsuccess = function() {\n-        resultsByName = reqName.result.filter(function(aResult) {\n-          return filterFacebook(aResult, options);\n-        });\n-        notifyFindNameReady();\n-      };\n-\n-      reqName.onerror = function(e) {\n-        window.console.warn('Error while trying to find by name: ',\n-                                e.target.error.name);\n-        notifyFindNameReady();\n-      };\n-    }\n-    else {\n-      notifyFindNameReady();\n-    }\n-\n-    if (isEmptyStr(aContact.familyName)) {\n-      endOfMatchByName(finalResult, aContact, resultsByName, callbacks);\n-    }\n-    else {\n-      var targetFamilyName = aContact.familyName[0].trim();\n-      // Filter by familyName using startsWith. Gecko 'startsWith' operation\n-      // acts as 'equal' but does not match case.\n-      var reqFamilyName = dataProvider.find({\n-        filterValue: targetFamilyName,\n-        filterBy: ['familyName'],\n-        filterOp: 'startsWith'\n-      });\n-\n-      reqFamilyName.onsuccess = function() {\n-        var results = reqFamilyName.result;\n-\n-        var givenNames = [];\n-        var targetGN = null;\n-\n-        if (!isEmptyStr(aContact.givenName)) {\n-          targetGN = Normalizer.toAscii(\n-                            aContact.givenName[0].trim().toLowerCase()).\n-                            replace(blankRegExp, '');\n-        }\n-\n-        results.forEach(function(mContact) {\n-          if (mContact.id === aContact.id || isEmptyStr(mContact.givenName)) {\n-            return;\n-          }\n-          givenNames.push({\n-            contact: mContact,\n-            givenName: Normalizer.toAscii(\n-                         mContact.givenName[0].trim().toLowerCase()).\n-                          replace(blankRegExp, '')\n-          });\n-        });\n-\n-        var finalMatchings = givenNames.filter(function(obj) {\n-          var gn = obj.givenName;\n-          return ((gn === targetGN || targetGN.startsWith(gn) ||\n-                  gn.startsWith(targetGN)) &&\n-                  filterFacebook(obj.contact, options));\n-        });\n-\n-        finalMatchings.forEach(function(aMatching) {\n-          finalResult[aMatching.contact.id] = {\n-            matchings: {\n-              'name': [{\n-                target: targetFamilyName,\n-                matchedValue: Array.isArray(aMatching.contact.name) ?\n-                              aMatching.contact.name[0] :\n-                              getCompleteName(aMatching.contact)\n-              }]\n-            },\n-            matchingContact: aMatching.contact\n-          };\n-        });\n-\n-        if (resultsByName) {\n-          endOfMatchByName(finalResult, aContact, resultsByName, callbacks);\n-        }\n-        else {\n-          document.addEventListener('by_name_ready', function nameReady() {\n-            document.removeEventListener('by_name_ready', nameReady);\n-            endOfMatchByName(finalResult, aContact, resultsByName, callbacks);\n-          });\n-        }\n-      };\n-\n-      reqFamilyName.onerror = function(e) {\n-        window.console.error('Error while trying to find by familyName: ',\n-                             e.target.error.name);\n-        notifyMismatch(callbacks);\n-      };\n-    }\n-  }\n-\n-  function getLinkedTo(contact) {\n-    var out = null;\n-\n-    if (Array.isArray(contact.category)) {\n-      var idx = contact.category.indexOf(FB_LINKED);\n-      if (idx !== -1) {\n-        out = contact.category[idx + 1];\n-      }\n-    }\n-    return out;\n-  }\n-\n-  function isFbLinked(contact) {\n-    return (Array.isArray(contact.category) &&\n-                        contact.category.indexOf(FB_LINKED) !== -1);\n-  }\n-\n-  function isFbContact(contact) {\n-    return (Array.isArray(contact.category) &&\n-                        contact.category.indexOf(FB_CATEGORY) !== -1);\n-  }\n-\n-  function filterFacebook(contact, linkParams) {\n-    var out = false;\n-\n-    if (!isFbContact(contact)) {\n-      out = true;\n-    }\n-    else if (isFbLinked(contact)) {\n-      var linkedTo = getLinkedTo(contact);\n-      var targetUid = linkParams.linkedTo || '';\n-      var linkedMatched = linkParams.linkedMatched || {};\n-\n-      if (targetUid === linkedTo) {\n-        out = true;\n-      }\n-      // It is only allowed to match one linked contact or various but all of\n-      // them linked to the same FB friend\n-      else if ((Object.keys(linkedMatched).length === 0 ||\n-               linkedMatched[linkedTo]) && !targetUid) {\n-        linkedMatched[linkedTo] = linkedTo;\n-        out = true;\n-      }\n-    }\n-\n-    return out;\n-  }\n-\n-  function endOfMatchByName(finalResult, aContact, resultsByName, callbacks) {\n-    resultsByName.forEach(function(aResult) {\n-      if (aResult.id === aContact.id) {\n-        return;\n-      }\n-\n-      var matchingObj = {\n-        matchings: {\n-          'name': [{\n-            target: (Array.isArray(aContact.name) &&\n-                     (typeof aContact.name[0] === 'string')) ?\n-                     aContact.name[0].trim() : getCompleteName(aContact),\n-            matchedValue: Array.isArray(aResult.name) ?\n-                          aResult.name[0] :\n-                          getCompleteName(aResult)\n-          }]\n-        },\n-        matchingContact: aResult\n-      };\n-\n-      if (!finalResult[aResult.id]) {\n-        finalResult[aResult.id] = matchingObj;\n-      }\n-      else {\n-        finalResult[aResult.id].matchings.name =\n-          matchingObj.matchings.name;\n-      }\n-    });\n-\n-    if (Object.keys(finalResult).length > 0) {\n-      notifyMatch(callbacks, finalResult);\n-    }\n-    else {\n-      notifyMismatch(callbacks);\n-    }\n-  }\n-\n-  function getCompleteName(contact) {\n-    var givenName = Array.isArray(contact.givenName) ?\n-                    contact.givenName[0] : '';\n-\n-    var familyName = Array.isArray(contact.familyName) ?\n-                    contact.familyName[0] : '';\n-\n-    var completeName = givenName && familyName ?\n-                       givenName + ' ' + familyName :\n-                       givenName || familyName;\n-\n-    return completeName;\n-  }\n-\n-  function isEmpty(collection) {\n-    return ((!Array.isArray(collection) || !collection[0]) || (collection[0] &&\n-                          collection[0].value && !collection[0].value.trim()));\n-  }\n-\n-  function isEmptyStr(collection) {\n-    return (!Array.isArray(collection) ||\n-            (typeof collection[0] !== 'string') || !(collection[0].trim()));\n-  }\n-\n-  function hasName(aContact) {\n-    return (!isEmptyStr(aContact.givenName) ||\n-            !isEmptyStr(aContact.familyName));\n-  }\n-\n-  function reconcileResults(incomingContact, nameMatches, phoneMailMatches,\n-                            callbacks) {\n-    var finalMatchings = {};\n-\n-    // Name matches drive all the process\n-    Object.keys(nameMatches).forEach(function(aNameMatching) {\n-      var matchingContact = nameMatches[aNameMatching].matchingContact;\n-\n-      var isPhoneMatching = Array.isArray(phoneMailMatches[aNameMatching].\n-                                          matchings.tel);\n-      var isMailMatching = Array.isArray(phoneMailMatches[aNameMatching].\n-                                          matchings.email);\n-\n-      // Three cases under which a matching is considered\n-      if (isPhoneMatching && isMailMatching) {\n-        finalMatchings[aNameMatching] = phoneMailMatches[aNameMatching];\n-      }\n-      else if (isPhoneMatching && (isEmpty(incomingContact.email) ||\n-              isEmpty(matchingContact.email))) {\n-        finalMatchings[aNameMatching] = phoneMailMatches[aNameMatching];\n-      }\n-      else if (isMailMatching && (isEmpty(incomingContact.tel) ||\n-                                  isEmpty(matchingContact.tel))) {\n-        finalMatchings[aNameMatching] = phoneMailMatches[aNameMatching];\n-      }\n-    });\n-\n-    if (Object.keys(finalMatchings).length > 0) {\n-      notifyMatch(callbacks, finalMatchings);\n-    }\n-    else {\n-      notifyMismatch(callbacks);\n-    }\n-  }\n-  \n-  // Exported method\n-  this.match = doMatch;\n-  \n-  Object.defineProperty(this, 'dataProvider', {\n-    set: function(theProvider) {\n-      dataProvider = theProvider;\n-    }\n-  });\n-};\n-\n-contacts.Matcher = new contacts.MatcherObj(navigator.mozContacts);\n+(function(exports) {\n+  exports.Matcher = new MatcherObj(navigator.mozContacts);","path":"shared/js/contacts/contacts_matcher.js","position":713,"original_position":713,"commit_id":"021342d38d78f1097da2ac7dcf04320ff6506978","original_commit_id":"021342d38d78f1097da2ac7dcf04320ff6506978","user":{"login":"arcturus","id":60801,"avatar_url":"https://avatars.githubusercontent.com/u/60801?v=3","gravatar_id":"","url":"https://api.github.com/users/arcturus","html_url":"https://github.com/arcturus","followers_url":"https://api.github.com/users/arcturus/followers","following_url":"https://api.github.com/users/arcturus/following{/other_user}","gists_url":"https://api.github.com/users/arcturus/gists{/gist_id}","starred_url":"https://api.github.com/users/arcturus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/arcturus/subscriptions","organizations_url":"https://api.github.com/users/arcturus/orgs","repos_url":"https://api.github.com/users/arcturus/repos","events_url":"https://api.github.com/users/arcturus/events{/privacy}","received_events_url":"https://api.github.com/users/arcturus/received_events","type":"User","site_admin":false},"body":"Leaving this file requires to have imported before the 'matcher_obj.js' file, why don't keep one single file, this one, that is already imported and we don't have to add more dependencies, but this file defines the `Matcher` singleton.\r\n\r\nI like to remove the 'kind of' namespace contact and have access to the object directly.","created_at":"2015-06-10T12:18:09Z","updated_at":"2015-06-10T12:18:09Z","html_url":"https://github.com/mozilla-b2g/gaia/pull/30517#discussion_r32111010","pull_request_url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/30517","_links":{"self":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/comments/32111010"},"html":{"href":"https://github.com/mozilla-b2g/gaia/pull/30517#discussion_r32111010"},"pull_request":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/30517"}}},"pull_request":{"url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/30517","id":37347651,"html_url":"https://github.com/mozilla-b2g/gaia/pull/30517","diff_url":"https://github.com/mozilla-b2g/gaia/pull/30517.diff","patch_url":"https://github.com/mozilla-b2g/gaia/pull/30517.patch","issue_url":"https://api.github.com/repos/mozilla-b2g/gaia/issues/30517","number":30517,"state":"open","locked":false,"title":"Bug 1173106 - [Contacts][NGA] Move contacts.Matcher to it own Object.…","user":{"login":"borjasalguero","id":1785537,"avatar_url":"https://avatars.githubusercontent.com/u/1785537?v=3","gravatar_id":"","url":"https://api.github.com/users/borjasalguero","html_url":"https://github.com/borjasalguero","followers_url":"https://api.github.com/users/borjasalguero/followers","following_url":"https://api.github.com/users/borjasalguero/following{/other_user}","gists_url":"https://api.github.com/users/borjasalguero/gists{/gist_id}","starred_url":"https://api.github.com/users/borjasalguero/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/borjasalguero/subscriptions","organizations_url":"https://api.github.com/users/borjasalguero/orgs","repos_url":"https://api.github.com/users/borjasalguero/repos","events_url":"https://api.github.com/users/borjasalguero/events{/privacy}","received_events_url":"https://api.github.com/users/borjasalguero/received_events","type":"User","site_admin":false},"body":"… r=arcturus","created_at":"2015-06-10T11:14:31Z","updated_at":"2015-06-10T12:18:09Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4adb917ec70f95a87c804626b4e5fae9c1cc02ad","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/30517/commits","review_comments_url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/30517/comments","review_comment_url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mozilla-b2g/gaia/issues/30517/comments","statuses_url":"https://api.github.com/repos/mozilla-b2g/gaia/statuses/021342d38d78f1097da2ac7dcf04320ff6506978","head":{"label":"borjasalguero:bug_1173106","ref":"bug_1173106","sha":"021342d38d78f1097da2ac7dcf04320ff6506978","user":{"login":"borjasalguero","id":1785537,"avatar_url":"https://avatars.githubusercontent.com/u/1785537?v=3","gravatar_id":"","url":"https://api.github.com/users/borjasalguero","html_url":"https://github.com/borjasalguero","followers_url":"https://api.github.com/users/borjasalguero/followers","following_url":"https://api.github.com/users/borjasalguero/following{/other_user}","gists_url":"https://api.github.com/users/borjasalguero/gists{/gist_id}","starred_url":"https://api.github.com/users/borjasalguero/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/borjasalguero/subscriptions","organizations_url":"https://api.github.com/users/borjasalguero/orgs","repos_url":"https://api.github.com/users/borjasalguero/repos","events_url":"https://api.github.com/users/borjasalguero/events{/privacy}","received_events_url":"https://api.github.com/users/borjasalguero/received_events","type":"User","site_admin":false},"repo":{"id":4470618,"name":"gaia","full_name":"borjasalguero/gaia","owner":{"login":"borjasalguero","id":1785537,"avatar_url":"https://avatars.githubusercontent.com/u/1785537?v=3","gravatar_id":"","url":"https://api.github.com/users/borjasalguero","html_url":"https://github.com/borjasalguero","followers_url":"https://api.github.com/users/borjasalguero/followers","following_url":"https://api.github.com/users/borjasalguero/following{/other_user}","gists_url":"https://api.github.com/users/borjasalguero/gists{/gist_id}","starred_url":"https://api.github.com/users/borjasalguero/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/borjasalguero/subscriptions","organizations_url":"https://api.github.com/users/borjasalguero/orgs","repos_url":"https://api.github.com/users/borjasalguero/repos","events_url":"https://api.github.com/users/borjasalguero/events{/privacy}","received_events_url":"https://api.github.com/users/borjasalguero/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/borjasalguero/gaia","description":"Gaia is a HTML5-based Phone UI for the Boot 2 Gecko Project","fork":true,"url":"https://api.github.com/repos/borjasalguero/gaia","forks_url":"https://api.github.com/repos/borjasalguero/gaia/forks","keys_url":"https://api.github.com/repos/borjasalguero/gaia/keys{/key_id}","collaborators_url":"https://api.github.com/repos/borjasalguero/gaia/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/borjasalguero/gaia/teams","hooks_url":"https://api.github.com/repos/borjasalguero/gaia/hooks","issue_events_url":"https://api.github.com/repos/borjasalguero/gaia/issues/events{/number}","events_url":"https://api.github.com/repos/borjasalguero/gaia/events","assignees_url":"https://api.github.com/repos/borjasalguero/gaia/assignees{/user}","branches_url":"https://api.github.com/repos/borjasalguero/gaia/branches{/branch}","tags_url":"https://api.github.com/repos/borjasalguero/gaia/tags","blobs_url":"https://api.github.com/repos/borjasalguero/gaia/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/borjasalguero/gaia/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/borjasalguero/gaia/git/refs{/sha}","trees_url":"https://api.github.com/repos/borjasalguero/gaia/git/trees{/sha}","statuses_url":"https://api.github.com/repos/borjasalguero/gaia/statuses/{sha}","languages_url":"https://api.github.com/repos/borjasalguero/gaia/languages","stargazers_url":"https://api.github.com/repos/borjasalguero/gaia/stargazers","contributors_url":"https://api.github.com/repos/borjasalguero/gaia/contributors","subscribers_url":"https://api.github.com/repos/borjasalguero/gaia/subscribers","subscription_url":"https://api.github.com/repos/borjasalguero/gaia/subscription","commits_url":"https://api.github.com/repos/borjasalguero/gaia/commits{/sha}","git_commits_url":"https://api.github.com/repos/borjasalguero/gaia/git/commits{/sha}","comments_url":"https://api.github.com/repos/borjasalguero/gaia/comments{/number}","issue_comment_url":"https://api.github.com/repos/borjasalguero/gaia/issues/comments{/number}","contents_url":"https://api.github.com/repos/borjasalguero/gaia/contents/{+path}","compare_url":"https://api.github.com/repos/borjasalguero/gaia/compare/{base}...{head}","merges_url":"https://api.github.com/repos/borjasalguero/gaia/merges","archive_url":"https://api.github.com/repos/borjasalguero/gaia/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/borjasalguero/gaia/downloads","issues_url":"https://api.github.com/repos/borjasalguero/gaia/issues{/number}","pulls_url":"https://api.github.com/repos/borjasalguero/gaia/pulls{/number}","milestones_url":"https://api.github.com/repos/borjasalguero/gaia/milestones{/number}","notifications_url":"https://api.github.com/repos/borjasalguero/gaia/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/borjasalguero/gaia/labels{/name}","releases_url":"https://api.github.com/repos/borjasalguero/gaia/releases{/id}","created_at":"2012-05-28T13:04:39Z","updated_at":"2015-04-13T11:14:39Z","pushed_at":"2015-06-10T11:13:43Z","git_url":"git://github.com/borjasalguero/gaia.git","ssh_url":"git@github.com:borjasalguero/gaia.git","clone_url":"https://github.com/borjasalguero/gaia.git","svn_url":"https://github.com/borjasalguero/gaia","homepage":"https://wiki.mozilla.org/B2G","size":907952,"stargazers_count":1,"watchers_count":1,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"mozilla-b2g:master","ref":"master","sha":"27e4c1af0be302150a5f6e428666e0e3daf2a4ea","user":{"login":"mozilla-b2g","id":1261292,"avatar_url":"https://avatars.githubusercontent.com/u/1261292?v=3","gravatar_id":"","url":"https://api.github.com/users/mozilla-b2g","html_url":"https://github.com/mozilla-b2g","followers_url":"https://api.github.com/users/mozilla-b2g/followers","following_url":"https://api.github.com/users/mozilla-b2g/following{/other_user}","gists_url":"https://api.github.com/users/mozilla-b2g/gists{/gist_id}","starred_url":"https://api.github.com/users/mozilla-b2g/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mozilla-b2g/subscriptions","organizations_url":"https://api.github.com/users/mozilla-b2g/orgs","repos_url":"https://api.github.com/users/mozilla-b2g/repos","events_url":"https://api.github.com/users/mozilla-b2g/events{/privacy}","received_events_url":"https://api.github.com/users/mozilla-b2g/received_events","type":"Organization","site_admin":false},"repo":{"id":2317369,"name":"gaia","full_name":"mozilla-b2g/gaia","owner":{"login":"mozilla-b2g","id":1261292,"avatar_url":"https://avatars.githubusercontent.com/u/1261292?v=3","gravatar_id":"","url":"https://api.github.com/users/mozilla-b2g","html_url":"https://github.com/mozilla-b2g","followers_url":"https://api.github.com/users/mozilla-b2g/followers","following_url":"https://api.github.com/users/mozilla-b2g/following{/other_user}","gists_url":"https://api.github.com/users/mozilla-b2g/gists{/gist_id}","starred_url":"https://api.github.com/users/mozilla-b2g/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mozilla-b2g/subscriptions","organizations_url":"https://api.github.com/users/mozilla-b2g/orgs","repos_url":"https://api.github.com/users/mozilla-b2g/repos","events_url":"https://api.github.com/users/mozilla-b2g/events{/privacy}","received_events_url":"https://api.github.com/users/mozilla-b2g/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mozilla-b2g/gaia","description":"Gaia is a HTML5-based Phone UI for the Boot 2 Gecko Project. NOTE: For details of what branches are used for what releases, see","fork":false,"url":"https://api.github.com/repos/mozilla-b2g/gaia","forks_url":"https://api.github.com/repos/mozilla-b2g/gaia/forks","keys_url":"https://api.github.com/repos/mozilla-b2g/gaia/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mozilla-b2g/gaia/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mozilla-b2g/gaia/teams","hooks_url":"https://api.github.com/repos/mozilla-b2g/gaia/hooks","issue_events_url":"https://api.github.com/repos/mozilla-b2g/gaia/issues/events{/number}","events_url":"https://api.github.com/repos/mozilla-b2g/gaia/events","assignees_url":"https://api.github.com/repos/mozilla-b2g/gaia/assignees{/user}","branches_url":"https://api.github.com/repos/mozilla-b2g/gaia/branches{/branch}","tags_url":"https://api.github.com/repos/mozilla-b2g/gaia/tags","blobs_url":"https://api.github.com/repos/mozilla-b2g/gaia/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mozilla-b2g/gaia/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mozilla-b2g/gaia/git/refs{/sha}","trees_url":"https://api.github.com/repos/mozilla-b2g/gaia/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mozilla-b2g/gaia/statuses/{sha}","languages_url":"https://api.github.com/repos/mozilla-b2g/gaia/languages","stargazers_url":"https://api.github.com/repos/mozilla-b2g/gaia/stargazers","contributors_url":"https://api.github.com/repos/mozilla-b2g/gaia/contributors","subscribers_url":"https://api.github.com/repos/mozilla-b2g/gaia/subscribers","subscription_url":"https://api.github.com/repos/mozilla-b2g/gaia/subscription","commits_url":"https://api.github.com/repos/mozilla-b2g/gaia/commits{/sha}","git_commits_url":"https://api.github.com/repos/mozilla-b2g/gaia/git/commits{/sha}","comments_url":"https://api.github.com/repos/mozilla-b2g/gaia/comments{/number}","issue_comment_url":"https://api.github.com/repos/mozilla-b2g/gaia/issues/comments{/number}","contents_url":"https://api.github.com/repos/mozilla-b2g/gaia/contents/{+path}","compare_url":"https://api.github.com/repos/mozilla-b2g/gaia/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mozilla-b2g/gaia/merges","archive_url":"https://api.github.com/repos/mozilla-b2g/gaia/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mozilla-b2g/gaia/downloads","issues_url":"https://api.github.com/repos/mozilla-b2g/gaia/issues{/number}","pulls_url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls{/number}","milestones_url":"https://api.github.com/repos/mozilla-b2g/gaia/milestones{/number}","notifications_url":"https://api.github.com/repos/mozilla-b2g/gaia/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mozilla-b2g/gaia/labels{/name}","releases_url":"https://api.github.com/repos/mozilla-b2g/gaia/releases{/id}","created_at":"2011-09-03T01:38:46Z","updated_at":"2015-06-09T05:38:34Z","pushed_at":"2015-06-10T11:14:31Z","git_url":"git://github.com/mozilla-b2g/gaia.git","ssh_url":"git@github.com:mozilla-b2g/gaia.git","clone_url":"https://github.com/mozilla-b2g/gaia.git","svn_url":"https://github.com/mozilla-b2g/gaia","homepage":"https://wiki.mozilla.org/B2G","size":1631998,"stargazers_count":1940,"watchers_count":1940,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":2332,"mirror_url":null,"open_issues_count":1019,"forks":2332,"open_issues":1019,"watchers":1940,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/30517"},"html":{"href":"https://github.com/mozilla-b2g/gaia/pull/30517"},"issue":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/issues/30517"},"comments":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/issues/30517/comments"},"review_comments":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/30517/comments"},"review_comment":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/30517/commits"},"statuses":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/statuses/021342d38d78f1097da2ac7dcf04320ff6506978"}}}}