<commit_before>// =============================================================================
// Boost.js | Browser Utilities
// (c) Mathigon
// =============================================================================



import { throttle, Evented, words } from '@mathigon/core';
import { $, $body } from './elements';


// -----------------------------------------------------------------------------
// Utilities

const browserEvents = new Evented();
const ua = window.navigator.userAgent.toLowerCase();
const mobileRegex = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i;

export function redraw() {
  document.body.offsetHeight; /* jshint ignore:line */
}


// -----------------------------------------------------------------------------
// Resize Events

let width = window.innerWidth;
let height = window.innerHeight;

window.addEventListener('resize', () => {
  const newWidth = window.innerWidth;
  const newHeight = window.innerHeight;

  if (width === newWidth && height === newHeight) return;

  width = newWidth;
  height = newHeight;

  browserEvents.trigger('resize', { width, height });
  $body.trigger('scroll', { top: $body.scrollTop });
});

const doResize = throttle(function() {
  width = window.innerWidth;
  height = window.innerHeight;
  browserEvents.trigger('resize', { width, height });
});

function resize(fn = null) {
  if (fn) {
    fn({ width, height });
    browserEvents.on('resize', fn);
  } else {
    doResize();
  }
}


// -----------------------------------------------------------------------------
// Load Events

const loadQueue = [];
let loaded = false;

function afterLoad() {
  if (loaded) return;
  loaded = true;
  for (const fn of loadQueue) fn();
  setTimeout(resize);
}

window.onload = afterLoad;
document.addEventListener('DOMContentLoaded', afterLoad);

function ready(fn) {
  if (loaded) {
    fn();
  } else {
    loadQueue.push(fn);
  }
}


// -----------------------------------------------------------------------------
// Cookies
// TODO Refactor this!

function getCookies() {
  const pairs = document.cookie.split(';');
  const result = {};
  for (let i = 0, n = pairs.length; i < n; ++i) {
    const pair = pairs[i].split('=');
    pair[0] = pair[0].replace(/^\s+|\s+$/, '');
    result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
  }
  return result;
}

function getCookie(name) {
  const v = document.cookie.match(new RegExp(`(^|;) ?${name}=([^;]*)(;|$)`));
  return v ? v[2] : null;
}

function setCookie(name, value, maxAge = 60 * 60* 24 * 365) {
  document.cookie = name + '=' + value + ';path=/;max-age=' + maxAge;
}

function deleteCookie(name) {
  setCookie(name, '', -1);
}


// -----------------------------------------------------------------------------
// Storage

const STORAGE_KEY = '_M';

function setStorage(key, value) {
  const keys = (key||'').split('.');
  const storage = JSON.parse(window.localStorage.getItem(STORAGE_KEY)) || {};
  let path = storage;

  for (let i=0; i<keys.length-1; ++i) {
    if (path[keys[i]] == null) path[keys[i]] = {};
    path = path[keys[i]];
  }

  path[keys[keys.length - 1]] = value;
  window.localStorage.setItem(STORAGE_KEY, JSON.stringify(storage));
}

function getStorage(key) {
  let path = JSON.parse(window.localStorage.getItem(STORAGE_KEY)) || {};
  if (!key) return path;

  const keys = (key||'').split('.');
  const lastKey = keys.pop();

  for (const k of keys) {
    if (!(k in path)) return null;
    path = path[k];
  }
  return path[lastKey]
}

function deleteStorage(key) {
  if (key) {
    setStorage(key, null);
  } else {
    window.localStorage.setItem(STORAGE_KEY, '');
  }
}


// -----------------------------------------------------------------------------
// Keyboard Events

export const KEY_CODES = {
  backspace: 8,
  tab: 9,
  enter: 13,
  shift: 16,
  ctrl: 17,
  alt: 18,
  pause: 19,
  capslock: 20,
  escape: 27,
  space: 32,
  pageup: 33,
  pagedown: 34,
  end: 35,
  home: 36,
  left: 37,
  up: 38,
  right: 39,
  down: 40,
  insert: 45,
  'delete': 46
};

function activeInput() {
  const active = document.activeElement;
  return active === document.body ? undefined : $(active);
}

// Executes fn if any one of [keys] is pressed
function onKey(keys, fn) {
  keys = words(keys).map(k => KEY_CODES[k] || k);
  document.addEventListener('keydown', function(e) {
    const $active = activeInput();
    if ($active && $active.is('input, textarea, [contenteditable]')) return;
    if (keys.indexOf(e.keyCode) >= 0) fn(e);
  });
}


// -----------------------------------------------------------------------------
// Polyfill for external SVG imports

const IEUA = /\bTrident\/[567]\b|\bMSIE (?:9|10)\.0\b/;
const webkitUA = /\bAppleWebKit\/(\d+)\b/;
const EdgeUA = /\bEdge\/12\.(\d+)\b/;

const polyfill = IEUA.test(navigator.userAgent) ||
  (navigator.userAgent.match(EdgeUA) || [])[1] < 10547 ||
  (navigator.userAgent.match(webkitUA) || [])[1] < 537;

const requests = {};
<commit_msg>Send key name with onKey callback, and allow listening to keyUp<commit_after>// =============================================================================
// Boost.js | Browser Utilities
// (c) Mathigon
// =============================================================================



import { throttle, Evented, words } from '@mathigon/core';
import { $, $body } from './elements';


// -----------------------------------------------------------------------------
// Utilities

const browserEvents = new Evented();
const ua = window.navigator.userAgent.toLowerCase();
const mobileRegex = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i;

export function redraw() {
  document.body.offsetHeight; /* jshint ignore:line */
}


// -----------------------------------------------------------------------------
// Resize Events

let width = window.innerWidth;
let height = window.innerHeight;

window.addEventListener('resize', () => {
  const newWidth = window.innerWidth;
  const newHeight = window.innerHeight;

  if (width === newWidth && height === newHeight) return;

  width = newWidth;
  height = newHeight;

  browserEvents.trigger('resize', { width, height });
  $body.trigger('scroll', { top: $body.scrollTop });
});

const doResize = throttle(function() {
  width = window.innerWidth;
  height = window.innerHeight;
  browserEvents.trigger('resize', { width, height });
});

function resize(fn = null) {
  if (fn) {
    fn({ width, height });
    browserEvents.on('resize', fn);
  } else {
    doResize();
  }
}


// -----------------------------------------------------------------------------
// Load Events

const loadQueue = [];
let loaded = false;

function afterLoad() {
  if (loaded) return;
  loaded = true;
  for (const fn of loadQueue) fn();
  setTimeout(resize);
}

window.onload = afterLoad;
document.addEventListener('DOMContentLoaded', afterLoad);

function ready(fn) {
  if (loaded) {
    fn();
  } else {
    loadQueue.push(fn);
  }
}


// -----------------------------------------------------------------------------
// Cookies
// TODO Refactor this!

function getCookies() {
  const pairs = document.cookie.split(';');
  const result = {};
  for (let i = 0, n = pairs.length; i < n; ++i) {
    const pair = pairs[i].split('=');
    pair[0] = pair[0].replace(/^\s+|\s+$/, '');
    result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
  }
  return result;
}

function getCookie(name) {
  const v = document.cookie.match(new RegExp(`(^|;) ?${name}=([^;]*)(;|$)`));
  return v ? v[2] : null;
}

function setCookie(name, value, maxAge = 60 * 60* 24 * 365) {
  document.cookie = name + '=' + value + ';path=/;max-age=' + maxAge;
}

function deleteCookie(name) {
  setCookie(name, '', -1);
}


// -----------------------------------------------------------------------------
// Storage

const STORAGE_KEY = '_M';

function setStorage(key, value) {
  const keys = (key||'').split('.');
  const storage = JSON.parse(window.localStorage.getItem(STORAGE_KEY)) || {};
  let path = storage;

  for (let i=0; i<keys.length-1; ++i) {
    if (path[keys[i]] == null) path[keys[i]] = {};
    path = path[keys[i]];
  }

  path[keys[keys.length - 1]] = value;
  window.localStorage.setItem(STORAGE_KEY, JSON.stringify(storage));
}

function getStorage(key) {
  let path = JSON.parse(window.localStorage.getItem(STORAGE_KEY)) || {};
  if (!key) return path;

  const keys = (key||'').split('.');
  const lastKey = keys.pop();

  for (const k of keys) {
    if (!(k in path)) return null;
    path = path[k];
  }
  return path[lastKey]
}

function deleteStorage(key) {
  if (key) {
    setStorage(key, null);
  } else {
    window.localStorage.setItem(STORAGE_KEY, '');
  }
}


// -----------------------------------------------------------------------------
// Keyboard Events

export const KEY_CODES = {
  backspace: 8,
  tab: 9,
  enter: 13,
  shift: 16,
  ctrl: 17,
  alt: 18,
  pause: 19,
  capslock: 20,
  escape: 27,
  space: 32,
  pageup: 33,
  pagedown: 34,
  end: 35,
  home: 36,
  left: 37,
  up: 38,
  right: 39,
  down: 40,
  insert: 45,
  'delete': 46
};

function activeInput() {
  const active = document.activeElement;
  return active === document.body ? undefined : $(active);
}

// Executes fn if any one of [keys] is pressed
function onKey(keys, fn, up = false) {
  const keyNames = words(keys);
  const keyCodes = keyNames.map(k => KEY_CODES[k] || k);
  const event = up ? 'keyup' : 'keydown';

  document.addEventListener(event, function(e) {
    const $active = activeInput();
    if ($active && $active.is('input, textarea, [contenteditable]')) return;

    const i = keyCodes.indexOf(e.keyCode);
    if (i >= 0) fn(e, keyNames[i]);
  });
}


// -----------------------------------------------------------------------------
// Polyfill for external SVG imports

const IEUA = /\bTrident\/[567]\b|\bMSIE (?:9|10)\.0\b/;
const webkitUA = /\bAppleWebKit\/(\d+)\b/;
const EdgeUA = /\bEdge\/12\.(\d+)\b/;

const polyfill = IEUA.test(navigator.userAgent) ||
  (navigator.userAgent.match(EdgeUA) || [])[1] < 10547 ||
  (navigator.userAgent.match(webkitUA) || [])[1] < 537;

const requests = {};

{'fraction_non_alphanumeric': 0.29604028833810603, 'fraction_numerical': 0.015206872716500444, 'mean_word_length': 2.3603185136031852, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 9, 'https://': 0, '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': '177100', 'n_tokens_mistral': 3344, 'n_tokens_neox': 3153, 'n_words': 741}