<commit_before>(function() {
  const api = 'https://emojityper.appspot.com';

  function historicData() {
    let p = window.fetch('https://emojityper.com/res/js/emojimap.js').then(out => out.text());
    p = p.then(raw => {
      const first = raw.indexOf('{');
      if (first === -1) {
        throw new Error('invalid emojimap');
      }
      // FIXME: never do this :O
      eval('var emojimap = ' + raw.substr(first));
      return emojimap;
    })
    p = p.then(emojimap => {
      const out = {};
      Object.keys(emojimap).forEach(string => {
        const data = emojimap[string];
        string = string.replace(/\s+/g, '').replace(/[’’]/g, '');
        if (!(string in out)) {
          out[string] = [];
        }
        out[string].push(...data.map(details => details.emoji));
      });

      return out;
    });
    return p;
  }

  function futureData() {
    return window.fetch(api + '/popular').then(out => out.json());
  }

  // const data = historicData();
  const data = futureData();
  let indexed = data.then(emojimap => {
    const prefixLength = 3;   // generate prefixes up to this length
    const maxSuggestions = 10;  // only generate this many suggestions
    const prefixSuggest = {};

    for (let k in emojimap) {
      const prefix = k.substr(0, prefixLength);
      for (let i = 1; i <= prefix.length; ++i) {
        const part = prefix.substr(0, i);
        let opts = prefixSuggest[part];
        if (!opts) {
          opts = prefixSuggest[part] = [];
        }
        if (opts.length < maxSuggestions) {
          opts.push(k);
        }
      }
    }

    return function(typed) {
      typed = typed.toLowerCase();
      const rest = typed.substr(prefixLength);
      let all = prefixSuggest[typed.substr(0, prefixLength)] || [];

      if (rest) {
        all = all.filter(word => word.substr(prefixLength).startsWith(rest));
      }
      all = all.map(word => {
        return {'name': word, 'options': emojimap[word] || []};
      });

      return all.length ? all : null;
    }
  });

  let timeout;
  const performRequest = (text, prefix) => {
    if (!text) {
      window.emojimanager.callback(null);
      return;
    }

    function send(out) {
      if (timeout === localTimeout) {
        window.emojimanager.callback(out);
      } else {
        console.debug('got results for old query');
      }
    }

    // TODO: only send extra query if there's not enough results, or the user hits 'more'
    window.clearTimeout(timeout);
    localTimeout = window.setTimeout(_ => {
      const data = new FormData();
      data.set('q', text);
      data.set('prefix', prefix)
      window.fetch(api + '/query', {method: 'POST', data}).then(out => out.json()).then(send);
    }, 2000);
    timeout = localTimeout;

    indexed.then(suggest => {
      let results = suggest(text);
      if (!prefix) {
        results = results.filter(result => result['name'] === text);
      }
      // FIXME: don't return results if request has changed
      send(results);
    }).catch(err => {
      console.warn('some error in suggest', err);
    });
  };

  window.emojimanager = {
    callback: function() {},
    request(text, prefix) {
      window.requestAnimationFrame(_ => {
        performRequest(text, prefix);
      });
    },
    submit(name, value) {
      // const data = `name=${name}&emoji=${value}`;
      // FIXME: we've disabled CORS for now; the response is actually useless anyway
      // FIXME: however, the client now thinks this succeeds even when it 400s
      const data = new FormData();
      data.set('name', name);
      data.set('emoji', value);
      return window.fetch(api + '/name', {method: 'POST', data, mode: 'no-cors'});
    },
  };

}())
<commit_msg>set => append for Safari/others<commit_after>(function() {
  const api = 'https://emojityper.appspot.com';

  function historicData() {
    let p = window.fetch('https://emojityper.com/res/js/emojimap.js').then(out => out.text());
    p = p.then(raw => {
      const first = raw.indexOf('{');
      if (first === -1) {
        throw new Error('invalid emojimap');
      }
      // FIXME: never do this :O
      eval('var emojimap = ' + raw.substr(first));
      return emojimap;
    })
    p = p.then(emojimap => {
      const out = {};
      Object.keys(emojimap).forEach(string => {
        const data = emojimap[string];
        string = string.replace(/\s+/g, '').replace(/[’’]/g, '');
        if (!(string in out)) {
          out[string] = [];
        }
        out[string].push(...data.map(details => details.emoji));
      });

      return out;
    });
    return p;
  }

  function futureData() {
    return window.fetch(api + '/popular').then(out => out.json());
  }

  // const data = historicData();
  const data = futureData();
  let indexed = data.then(emojimap => {
    const prefixLength = 3;   // generate prefixes up to this length
    const maxSuggestions = 10;  // only generate this many suggestions
    const prefixSuggest = {};

    for (let k in emojimap) {
      const prefix = k.substr(0, prefixLength);
      for (let i = 1; i <= prefix.length; ++i) {
        const part = prefix.substr(0, i);
        let opts = prefixSuggest[part];
        if (!opts) {
          opts = prefixSuggest[part] = [];
        }
        if (opts.length < maxSuggestions) {
          opts.push(k);
        }
      }
    }

    return function(typed) {
      typed = typed.toLowerCase();
      const rest = typed.substr(prefixLength);
      let all = prefixSuggest[typed.substr(0, prefixLength)] || [];

      if (rest) {
        all = all.filter(word => word.substr(prefixLength).startsWith(rest));
      }
      all = all.map(word => {
        return {'name': word, 'options': emojimap[word] || []};
      });

      return all.length ? all : null;
    }
  });

  let timeout;
  const performRequest = (text, prefix) => {
    if (!text) {
      window.emojimanager.callback(null);
      return;
    }

    function send(out) {
      if (timeout === localTimeout) {
        window.emojimanager.callback(out);
      } else {
        console.debug('got results for old query');
      }
    }

    // TODO: only send extra query if there's not enough results, or the user hits 'more'
    window.clearTimeout(timeout);
    localTimeout = window.setTimeout(_ => {
      const data = new FormData();
      data.append('q', text);
      data.append('prefix', prefix)
      window.fetch(api + '/query', {method: 'POST', data}).then(out => out.json()).then(send);
    }, 2000);
    timeout = localTimeout;

    indexed.then(suggest => {
      let results = suggest(text);
      if (!prefix) {
        results = results.filter(result => result['name'] === text);
      }
      // FIXME: don't return results if request has changed
      send(results);
    }).catch(err => {
      console.warn('some error in suggest', err);
    });
  };

  window.emojimanager = {
    callback: function() {},
    request(text, prefix) {
      window.requestAnimationFrame(_ => {
        performRequest(text, prefix);
      });
    },
    submit(name, value) {
      // const data = `name=${name}&emoji=${value}`;
      // FIXME: we've disabled CORS for now; the response is actually useless anyway
      // FIXME: however, the client now thinks this succeeds even when it 400s
      const data = new FormData();
      data.append('name', name);
      data.append('emoji', value);
      return window.fetch(api + '/name', {method: 'POST', data, mode: 'no-cors'});
    },
  };

}())

{'fraction_non_alphanumeric': 0.16324079017618792, 'fraction_numerical': 0.00400427122263748, 'mean_word_length': 2.3096289752650176, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 36, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5642346', 'n_tokens_mistral': 2358, 'n_tokens_neox': 2298, 'n_words': 565}