[
  {
    "path": ".gitignore",
    "content": "node_modules/\ndist/\n.sass-cache/\ncss\n_config.yml\njs\n*.swp\nold\n*.log\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: required\ndist: trusty\nlanguage: node_js\nnode_js: 6\n\nbranches:\n  only:\n  - master\n  \nbefore_install:\n  - npm i -g npm@latest\n  - npm i -g typescript\n  - npm i -g node-sass\n\ninstall:\n  - npm i\n\ncache:\n  directories:\n    - \"node_modules\"\n\nenv:\n  global:\n    - secure: >-\n        QdHr5mVFKOyS/SmQPd7X6TOGhi4XhlJU+2w0O+RubOo/h2i1wLXD/f0DbAjEc0XfpTBSt9IDtUtqSuTi1YIUbafrqUwLFBUZ5VGYsPcMA48pD6+nzflMuaaCf//+pM8WtIAfgOK1i5k30faRzRnvSIYgX/Yyf4vS9j9OmfwD6342d1eD2b+h24R9z+eLlG9QaoJx9EAYhr1UAFbERiBlN40ThGU7uSkehzuYtGaQil8J8cV/MRqPCTSMAcZvpiUM7yGri11AWxJ5bOmyn7yVTFRBVWZDb4XaREXVTaEIXhvi/g+NO0e9WFETkw2cR7/Q6lw3kr/DqGz6Ks9XCOGppb1xILbvkUID/7pW+UKuzt6hc4AdoJLR+p0QtMWQn51oh1npGXzaMwjGGQ36gr+rdzc3Lukh08URzAFC2kywCg1sCsNCfoEH0hVD8OUhNQIkVOz530isjkZ8q9S1Us0ib5uviSZeA9XmfPBKe7aRxMY98WUvaeDCRmHqtmWy0nl9+ys4aI0197+4XlXijWeezd/ESLcWlZozKysAvTavC+hZPIBYz3Hpd6JOVzDGDgxWTpF91nCOJ+/gz/kHztAcaIC/iIVwIH4NhovuZ8t0kCKraEOWCnVkrikw9yK8unxgQfGWqU38ywUR7dN5if+u6Y67ih4ASnJ/LCqvUQvzy1o=\n\nbefore_script:\n  - tsc\n  - node-sass scss -o css\n\nscript:\n  - npm run publish\n\n"
  },
  {
    "path": ".vscode/cSpell.json",
    "content": "// cSpell Settings\n{\n    // Version of the setting file.  Always 0.1\n    \"version\": \"0.1\",\n    // language - current active spelling language\n    \"language\": \"en\",\n    // words - list of words to be always considered correct\n    \"words\": [\n        \"Msgs\",\n        \"jumpfm\",\n        \"keyboardjs\",\n        \"keycode\",\n        \"pagedown\",\n        \"pageup\",\n        \"shortway\"\n    ],\n    // flagWords - list of words to be always considered incorrect\n    // This is useful for offensive words and common spelling errors.\n    // For example \"hte\" should be \"the\"\n    \"flagWords\": [\n        \"hte\"\n    ]\n}"
  },
  {
    "path": "README.md",
    "content": "[![Build Status](https://travis-ci.org/Gilad-Kutiel-App/jumpfm.svg?branch=master)](https://travis-ci.org/Gilad-Kutiel-App/jumpfm)  [![Build status](https://ci.appveyor.com/api/projects/status/g9ggpk5578fq56x2?svg=true)](https://ci.appveyor.com/project/gkutiel/jumpfm) \n\n# About\n\nJumpFm is a cross platform<sup>1,2</sup> dual panel file manager with builtin superpowers. \n\n----\n<sup>1</sup>\nWindows built is not tested at all, install it at your own risk\n\n<sup>2</sup>\nFor a Mac release see [this fork](https://github.com/heywoodlh/jumpfm)\n\n# \\<dev/\\>\n\n## tl;dr.<sup>1,2</sup>\n```\ngit clone git@github.com:Gilad-Kutiel-App/jumpfm.git\nnpm i -g typescript electron\ncd jumpfm\nnpm i\ntsc -w\nsass --watch scss:css\nelectron .\n```\n---\n<sup>1</sup>\nYou might want to [change npm's default directory](https://docs.npmjs.com/getting-started/fixing-npm-permissions#option-2-change-npms-default-directory-to-another-directory)\n\n<sup>2</sup>\nUse different terminal for each of  ```tsc -w```, ```sass --watch scss:css```, ```electron .```\n\n## More \\<dev/\\>\n\nJumpFm is an [Electron](https://electron.atom.io/) based app.\nIt is written in [TypeScript](https://www.typescriptlang.org/).\nTo hack the code all you need is [node.js](https://nodejs.org/en/) a\n[decent editor](http://bit.ly/2wHIoSz) and a [sass compiler](http://sass-lang.com/).\nThis is how your terminal should looks like:\n\n![](/misc/dev.png)\n\n\n\n"
  },
  {
    "path": "app.js",
    "content": "const updater = require('electron-simple-updater');\nconst electron = require('electron')\nconst app = electron.app\nconst BrowserWindow = electron.BrowserWindow\n\nconst path = require('path')\nconst url = require('url')\n\n// const log = require('electron-log');\n\napp.on('window-all-closed', function() {\n    if (process.platform != 'darwin') {\n        app.quit();\n    }\n});\n\napp.on('ready', function() {\n    updater.init(\n        'https://raw.githubusercontent.com/Gilad-Kutiel-App/jumpfm/master/updates.json'\n    );\n\n    const { width, height } = electron.screen.getPrimaryDisplay().workAreaSize;\n    const w = parseInt(width * .8);\n    const h = parseInt(height * .8);\n\n    global.argv = process.argv\n        // log.transports.file.level = 'debug';\n        // log.transports.console.level = 'debug';\n\n    // Create the browser window.\n    let mainWindow = new BrowserWindow({\n        width: w,\n        height: h,\n        icon: path.join(__dirname, 'build/icons/64x64.png'),\n    });\n\n    // mainWindow.setMenu(null);\n    mainWindow.loadURL('file://' + __dirname + '/index.html');\n\n    mainWindow.on('closed', function() {\n        mainWindow = null;\n    });\n});"
  },
  {
    "path": "appveyor.yml",
    "content": "os: unstable\nbranches:\n  only:\n  - master\n\ncache:\n  - node_modules\nenvironment:\n  GH_TOKEN:\n      secure: RcYdy07RY6uQXRBSJWfJfNG7FzZd7Y62IOTQ1n1pYjKTh9sgklNfNB9DBHZdOCzA\n  matrix:\n    - nodejs_version: 6\ninstall:\n  - ps: Install-Product node $env:nodejs_version\n  - set CI=true\n  - npm install -g npm@latest\n  - npm i -g typescript\n  - npm i -g node-sass \n  - set PATH=%APPDATA%\\npm;%PATH%\n  - npm install\nmatrix:\n  fast_finish: true\nbuild: off\nversion: '{build}'\nshallow_clone: true\nclone_depth: 1\ntest_script:\n  - tsc\n  - node-sass scss -o css\n  - npm run publish"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n  <title>JumpFm</title>\n  <link rel=\"stylesheet\"\n    href=\"css/index.css\">\n  <link rel=\"stylesheet\"\n    href=\"font-awesome.min.css\">\n\n  <script src=\"js/main.js\"></script>\n</head>\n\n<body>\n  <div id=\"app\"\n    v-bind:style=\"{ fontSize: fontSize + 'px' }\">\n\n    <!-- DIALOG -->\n    <div id='dialog'>\n      <div id='dialog-content'>\n        <div id='dialog-left'>\n          <div id='dialog-label'></div>\n        </div>\n        <div id='dialog-right'>\n          <input id=\"dialog-input\"\n            type=\"text\" />\n          <ol id='dialog-sug'\n            class='border'>\n            <li v-for=\"(sug, i) in dialog.sug\"\n              :cur=\"i == dialog.cur\"\n              v-html=\"sug.html\" />\n          </ol>\n        </div>\n      </div>\n    </div>\n    <!-- /DIALOG -->\n\n    <!-- PANELS  -->\n    <div id='panels'></div>\n    <!-- /PANELS -->\n\n    <!-- STATUS BAR  -->\n    <footer id=\"statusbar\">\n\n      <!-- MSGS  -->\n      <div id=\"statusbar-msgs\"></div>\n\n      <!-- BUTTONS  -->\n      <div id=\"statusbar-buttons\"></div>\n    </footer>\n    <!-- /STATUS BAR  -->\n  </div>\n</body>"
  },
  {
    "path": "misc/ascii.txt",
    "content": "\n\n       _                       ______          \n      | |                     |  ____|         \n      | |_   _ _ __ ___  _ __ | |__ _ __ ___   \n  _   | | | | | '_ ` _ \\| '_ \\|  __| '_ ` _ \\  \n | |__| | |_| | | | | | | |_) | |  | | | | | | \n  \\____/ \\__,_|_| |_| |_| .__/|_|  |_| |_| |_| \n                        | |                    \n                        |_|                    \n\n\n"
  },
  {
    "path": "misc/cookbook.txt",
    "content": "find {{pwd}} -name {{input}}\nfind {{sel}} -name {{input}}\ncat ~/favorits.txt"
  },
  {
    "path": "misc/icons.awk",
    "content": "#!/usr/bin/awk -f\nBEGIN { \n\tRS=\"}\"\n\tprint \"{\"\n}\n\nmatch($0, /icon: '([^']+)'.*extensions: (\\[[^\\].]+\\])/, a){\n\tprint \"\\\"\"a[1]\"\\\"\", \":\", gensub(/'/,\"\\\"\", \"g\", a[2])\",\"\n}\t\n\nmatch($0, /icon: '([^']+)'.*languages: (\\[[^\\]]+\\])/, a){\n\tprint \"\\\"\"a[1]\"\\\"\", \":\", gensub(/languages\\.(\\w+)/, \"\\\"\\\\1\\\"\", \"g\", a[2])\",\"\n}\t\n\nEND {\n\tprint \"}\"\n}\n"
  },
  {
    "path": "misc/icons.txt",
    "content": "/* tslint:disable max-line-length */\nimport { languages } from './languages';\nimport { FileFormat, IFileCollection } from '../models';\n\nexport const extensions: IFileCollection = {\n  default: {\n    file: { icon: 'file', format: FileFormat.svg },\n  },\n  supported: [\n    { icon: 'access', extensions: ['accdb', 'accdt', 'mdb', 'accda', 'accdc', 'accde', 'accdp', 'accdr', 'accdu', 'ade', 'adp', 'laccdb', 'ldb', 'mam', 'maq', 'mdw'], format: FileFormat.svg },\n    { icon: 'actionscript', extensions: [], languages: [languages.actionscript], format: FileFormat.svg },\n    { icon: 'ai', extensions: ['ai'], format: FileFormat.svg },\n    { icon: 'ai2', extensions: ['ai'], format: FileFormat.svg, disabled: true },\n    { icon: 'org', extensions: ['org'], format: FileFormat.svg },\n    { icon: 'angular', extensions: ['.angular-cli.json', 'angular-cli.json'], filename: true, format: FileFormat.svg },\n    { icon: 'ng_component_ts', extensions: ['component.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_component_js', extensions: ['component.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_controller_ts', extensions: ['controller.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_controller_js', extensions: ['controller.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_directive_ts', extensions: ['directive.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_directive_js', extensions: ['directive.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_guard_ts', extensions: ['guard.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_guard_js', extensions: ['guard.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_module_ts', extensions: ['module.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_module_js', extensions: ['module.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_pipe_ts', extensions: ['pipe.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_pipe_js', extensions: ['pipe.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_routing_ts', extensions: ['routing.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_routing_js', extensions: ['routing.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_routing_ts', extensions: ['app-routing.module.ts'], filename: true, format: FileFormat.svg, disabled: true },\n    { icon: 'ng_routing_js', extensions: ['app-routing.module.js'], filename: true, format: FileFormat.svg, disabled: true },\n    { icon: 'ng_smart_component_ts', extensions: ['page.ts', 'container.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_smart_component_js', extensions: ['page.js', 'container.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_service_ts', extensions: ['service.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_service_js', extensions: ['service.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_component_ts2', extensions: ['component.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_component_js2', extensions: ['component.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_directive_ts2', extensions: ['directive.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_directive_js2', extensions: ['directive.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_module_ts2', extensions: ['module.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_module_js2', extensions: ['module.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_pipe_ts2', extensions: ['pipe.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_pipe_js2', extensions: ['pipe.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_routing_ts2', extensions: ['routing.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_routing_js2', extensions: ['routing.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_routing_ts2', extensions: ['app-routing.module.ts'], filename: true, format: FileFormat.svg, disabled: true },\n    { icon: 'ng_routing_js2', extensions: ['app-routing.module.js'], filename: true, format: FileFormat.svg, disabled: true },\n    { icon: 'ng_smart_component_ts2', extensions: ['page.ts', 'container.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_smart_component_js2', extensions: ['page.js', 'container.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_service_ts2', extensions: ['service.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'ng_service_js2', extensions: ['service.js'], format: FileFormat.svg, disabled: true },\n    { icon: 'ansible', extensions: [], languages: [languages.ansible], format: FileFormat.svg },\n    { icon: 'anyscript', extensions: [], languages: [languages.anyscript], format: FileFormat.svg },\n    { icon: 'apache', extensions: [], languages: [languages.apache], format: FileFormat.svg },\n    { icon: 'apib', extensions: [], languages: [languages.apib], format: FileFormat.svg },\n    { icon: 'applescript', extensions: [], languages: [languages.applescript], format: FileFormat.svg },\n    { icon: 'appveyor', extensions: ['appveyor.yml', '.appveyor.yml'], filename: true, format: FileFormat.svg },\n    { icon: 'asp', extensions: [], languages: [languages.asp], format: FileFormat.svg },\n    { icon: 'aspx', extensions: ['aspx', 'ascx'], format: FileFormat.svg },\n    { icon: 'assembly', extensions: [], languages: [languages.assembly], format: FileFormat.svg },\n    { // https://en.wikipedia.org/wiki/Audio_file_format\n      icon: 'audio',\n      extensions: ['aac', 'act', 'aiff', 'amr', 'ape', 'au', 'dct', 'dss', 'dvf', 'flac', 'gsm',\n        'iklax', 'ivs', 'm4a', 'm4b', 'm4p', 'mmf', 'mogg', 'mp3', 'mpc', 'msv', 'oga', 'ogg', 'opus',\n        'ra', 'raw', 'tta', 'vox', 'wav', 'wma'],\n      format: FileFormat.svg,\n    },\n    { icon: 'aurelia', extensions: ['aurelia.json'], filename: true, format: FileFormat.svg },\n    { icon: 'autohotkey', extensions: [], languages: [languages.autohotkey], format: FileFormat.svg },\n    { icon: 'autoit', extensions: [], languages: [languages.autoit], format: FileFormat.svg },\n    { icon: 'aws', extensions: [], format: FileFormat.svg },\n    { icon: 'babel', extensions: ['.babelrc', 'babelrc.js'], light: true, filename: true, format: FileFormat.svg },\n    { icon: 'babel2', extensions: ['.babelrc', 'babelrc.js'], light: true, filename: true, format: FileFormat.svg, disabled: true },\n    { icon: 'bat', extensions: [], languages: [languages.bat], format: FileFormat.svg },\n    { // http://www.file-extensions.org/filetype/extension/name/binary-files\n      icon: 'binary',\n      extensions: ['a', 'app', 'bin', 'cmo', 'cmx', 'cma', 'cmxa', 'cmi', 'dll', 'exe', 'hl', 'ilk',\n        'lib', 'n', 'ndll', 'o', 'obj', 'pyc', 'pyd', 'pyo', 'pdb', 'scpt', 'scptd', 'so'],\n      format: FileFormat.svg,\n    },\n    { icon: 'bithound', extensions: ['.bithoundrc'], filename: true, format: FileFormat.svg },\n    { icon: 'blade', extensions: [], languages: [languages.blade], format: FileFormat.svg },\n    { icon: 'bolt', extensions: [], languages: [languages.bolt], filename: true, format: FileFormat.svg },\n    { icon: 'bower', extensions: ['.bowerrc', 'bower.json'], filename: true, format: FileFormat.svg },\n    { icon: 'buckbuild', extensions: ['.buckconfig'], filename: true, format: FileFormat.svg },\n    { icon: 'bundler', extensions: ['gemfile', 'gemfile.lock'], filename: true, format: FileFormat.svg },\n    { icon: 'c', extensions: [], languages: [languages.c], format: FileFormat.svg },\n    { icon: 'c2', extensions: [], languages: [languages.c], format: FileFormat.svg, disabled: true },\n    { icon: 'cabal', extensions: [], languages: [languages.cabal], format: FileFormat.svg },\n    { icon: 'cake', extensions: [], languages: [languages.cake], format: FileFormat.svg },\n    { icon: 'cakephp', extensions: [], format: FileFormat.svg },\n    { icon: 'cert', extensions: ['csr', 'crt', 'cer', 'der', 'pfx', 'p12', 'p7b', 'p7r', 'src', 'crl', 'sst', 'stl'], format: FileFormat.svg },\n    { icon: 'cf', extensions: ['lucee'], languages: [languages.coldfusion], format: FileFormat.svg },\n    { icon: 'cf2', extensions: ['lucee'], languages: [languages.coldfusion], format: FileFormat.svg, disabled: true },\n    { icon: 'cfc', extensions: [], languages: [languages.cfc], format: FileFormat.svg },\n    { icon: 'cfc2', extensions: [], languages: [languages.cfc], format: FileFormat.svg, disabled: true },\n    { icon: 'cfm', extensions: [], languages: [languages.cfm], format: FileFormat.svg },\n    { icon: 'cfm2', extensions: [], languages: [languages.cfm], format: FileFormat.svg, disabled: true },\n    { icon: 'cheader', extensions: ['h'], format: FileFormat.svg },\n    { icon: 'chef', extensions: ['chefignore', 'berksfile', 'berksfile.lock', 'policyfile'], filename: true, format: FileFormat.svg },\n    { icon: 'class', extensions: ['class'], format: FileFormat.svg },\n    { icon: 'circleci', extensions: ['circle.yml'], light: true, filename: true, format: FileFormat.svg },\n    { icon: 'clojure', extensions: ['cjm', 'cljc'], languages: [languages.clojure], format: FileFormat.svg },\n    { icon: 'cmake', extensions: [], languages: [languages.cmake, languages.cmakecache], format: FileFormat.svg },\n    { icon: 'cobol', extensions: [], languages: [languages.cobol], format: FileFormat.svg },\n    { icon: 'codeclimate', extensions: ['.codeclimate.yml'], light: true, filename: true, format: FileFormat.svg },\n    { icon: 'codecov', extensions: ['codecov.yml', '.codecov.yml'], filename: true, format: FileFormat.svg },\n    { icon: 'codekit', extensions: ['kit'], format: FileFormat.svg },\n    { icon: 'codekit', extensions: ['config.codekit', 'config.codekit2', 'config.codekit3'], filename: true, format: FileFormat.svg },\n    { icon: 'coffeescript', extensions: [], languages: [languages.coffeescript], format: FileFormat.svg },\n    { icon: 'config', extensions: ['env'], light: true, languages: [languages.properties], format: FileFormat.svg },\n    { icon: 'config', extensions: ['.env.example'], light: true, filename: true, format: FileFormat.svg },\n    { icon: 'compass', extensions: [], format: FileFormat.svg },\n    { icon: 'composer', extensions: ['composer.json', 'composer.lock'], filename: true, format: FileFormat.svg },\n    { icon: 'cpp', extensions: [], languages: [languages.cpp], format: FileFormat.svg },\n    { icon: 'cpp2', extensions: [], languages: [languages.cpp], format: FileFormat.svg, disabled: true },\n    { icon: 'cppheader', extensions: ['hpp'], format: FileFormat.svg },\n    { icon: 'crystal', extensions: [], languages: [languages.crystal], format: FileFormat.svg },\n    { icon: 'csharp', extensions: ['csx'], languages: [languages.csharp], format: FileFormat.svg },\n    { icon: 'csproj', extensions: ['csproj'], format: FileFormat.svg },\n    { icon: 'css', extensions: [], languages: [languages.css], format: FileFormat.svg },\n    { icon: 'csslint', extensions: ['.csslintrc'], filename: true, format: FileFormat.svg },\n    { icon: 'cssmap', extensions: ['css.map'], format: FileFormat.svg },\n    { icon: 'cucumber', extensions: [], languages: [languages.cucumber], format: FileFormat.svg },\n    { icon: 'dartlang', extensions: [], languages: [languages.dart], format: FileFormat.svg },\n    { icon: 'db', extensions: ['db'], light: true, format: FileFormat.svg },\n    { icon: 'delphi', extensions: [], languages: [languages.pascal], format: FileFormat.svg },\n    { icon: 'dlang', extensions: [], languages: [languages.dlang], format: FileFormat.svg },\n    { icon: 'diff', extensions: [], languages: [languages.diff], format: FileFormat.svg },\n    { icon: 'docker', extensions: ['.dockerignore', 'docker-compose.yml', 'docker-compose.ci-build.yml', 'docker-compose.override.yml', 'docker-compose.vs.debug.yml', 'docker-compose.vs.release.yml', 'docker-cloud.yml'], filename: true, languages: [languages.dockerfile], format: FileFormat.svg },\n    { icon: 'docker2', extensions: ['.dockerignore', 'docker-compose.yml', 'docker-compose.ci-build.yml', 'docker-compose.override.yml', 'docker-compose.vs.debug.yml', 'docker-compose.vs.release.yml', 'docker-cloud.yml'], filename: true, languages: [languages.dockerfile], format: FileFormat.svg, disabled: true },\n    { icon: 'doxygen', extensions: [], languages: [languages.doxygen], format: FileFormat.svg },\n    { icon: 'drone', extensions: ['.drone.yml', '.drone.yml.sig'], light: true, filename: true, format: FileFormat.svg },\n    { icon: 'editorconfig', extensions: ['.editorconfig'], filename: true, format: FileFormat.svg },\n    { icon: 'ejs', extensions: ['ejs'], format: FileFormat.svg },\n    { icon: 'elasticbeanstalk', extensions: [], format: FileFormat.svg },\n    { icon: 'elixir', extensions: [], languages: [languages.elixir], format: FileFormat.svg },\n    { icon: 'elm', extensions: ['elm-package.json'], filename: true, languages: [languages.elm], format: FileFormat.svg },\n    { icon: 'elm2', extensions: ['elm-package.json'], filename: true, languages: [languages.elm], format: FileFormat.svg, disabled: true },\n    { icon: 'emacs', extensions: ['el', 'elc'], format: FileFormat.svg },\n    { icon: 'ember', extensions: ['.ember-cli'], filename: true, format: FileFormat.svg },\n    { icon: 'ensime', extensions: ['ensime'], format: FileFormat.svg },\n    { icon: 'eps', extensions: ['eps'], format: FileFormat.svg },\n    { icon: 'erb', extensions: [], languages: [languages.erb], format: FileFormat.svg },\n    { icon: 'erlang', extensions: ['emakefile', '.emakerfile'], filename: true, languages: [languages.erlang], format: FileFormat.svg },\n    { icon: 'eslint', extensions: ['.eslintrc', '.eslintignore', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yaml', '.eslintrc.yml'], filename: true, format: FileFormat.svg },\n    { icon: 'eslint2', extensions: ['.eslintrc', '.eslintignore', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yaml', '.eslintrc.yml'], filename: true, format: FileFormat.svg, disabled: true },\n    { icon: 'excel', extensions: ['xls', 'xlsx', 'xlsm', 'ods'], format: FileFormat.svg },\n    { icon: 'favicon', extensions: ['favicon.ico'], filename: true, format: FileFormat.svg },\n    { icon: 'firebase', extensions: ['.firebaserc'], filename: true, format: FileFormat.svg },\n    { icon: 'flash', extensions: ['swf', 'swc'], format: FileFormat.svg },\n    { icon: 'flow', extensions: ['js.flow'], format: FileFormat.svg },\n    { icon: 'flow', extensions: ['.flowconfig'], filename: true, format: FileFormat.svg },\n    { icon: 'font', extensions: ['woff', 'woff2', 'ttf', 'otf', 'eot', 'pfa', 'pfb', 'sfd'], light: true, format: FileFormat.svg },\n    { icon: 'fortran', extensions: [], languages: [languages.fortran], format: FileFormat.svg },\n    { icon: 'fsharp', extensions: [], languages: [languages.fsharp], format: FileFormat.svg },\n    { icon: 'fsproj', extensions: ['fsproj'], format: FileFormat.svg },\n    { icon: 'freemarker', extensions: [], languages: [languages.freemarker], format: FileFormat.svg },\n    { icon: 'fusebox', extensions: ['fuse.js'], filename: true, format: FileFormat.svg },\n    { icon: 'galen', extensions: [], languages: [languages.galen], format: FileFormat.svg },\n    { icon: 'galen2', extensions: [], languages: [languages.galen], format: FileFormat.svg, disabled: true },\n    { icon: 'git', extensions: ['.gitattributes', '.gitconfig', '.gitignore', '.gitmodules', '.gitkeep'], filename: true, languages: [languages.git], format: FileFormat.svg },\n    { icon: 'gamemaker', extensions: ['gmx'], languages: [languages.gamemaker], format: FileFormat.svg },\n    { icon: 'gamemaker2', extensions: ['yy', 'yyp'], light: true, languages: [languages.gamemaker2], format: FileFormat.svg },\n    { icon: 'gamemaker81', extensions: [], languages: [languages.gamemaker81], format: FileFormat.svg },\n    { icon: 'gitlab', extensions: ['.gitlab-ci.yml'], filename: true, format: FileFormat.svg },\n    { icon: 'glsl', extensions: [], languages: [languages.glsl], format: FileFormat.svg },\n    { icon: 'go', extensions: [], languages: [languages.go], format: FileFormat.svg },\n    { icon: 'godot', extensions: [], languages: [languages.godot], format: FileFormat.svg },\n    { icon: 'gradle', extensions: ['gradle'], format: FileFormat.svg },\n    { icon: 'graphql', extensions: ['.gqlconfig'], filename: true, languages: [languages.graphql], format: FileFormat.svg },\n    { icon: 'graphviz', extensions: [], languages: [languages.graphviz], format: FileFormat.svg },\n    { icon: 'groovy', extensions: [], languages: [languages.groovy], format: FileFormat.svg },\n    { icon: 'groovy2', extensions: [], languages: [languages.groovy], format: FileFormat.svg, disabled: true },\n    {\n      icon: 'grunt', extensions: [\n        'gruntfile.coffee',\n        'gruntfile.babel.coffee',\n        'gruntfile.js',\n        'gruntfile.babel.js',\n        'gruntfile.ts',\n        'gruntfile.babel.ts',\n      ],\n      filename: true, format: FileFormat.svg,\n    },\n    {\n      icon: 'gulp', extensions: [\n        'gulpfile.coffee',\n        'gulpfile.babel.coffee',\n        'gulpfile.js',\n        'gulpfile.babel.js',\n        'gulpfile.ts',\n        'gulpfile.babel.ts',\n      ],\n      filename: true, format: FileFormat.svg,\n    },\n    { icon: 'haml', extensions: [], languages: [languages.haml], format: FileFormat.svg },\n    { icon: 'handlebars', extensions: [], languages: [languages.handlebars], format: FileFormat.svg },\n    { icon: 'handlebars2', extensions: [], languages: [languages.handlebars], format: FileFormat.svg, disabled: true },\n    { icon: 'harbour', extensions: [], languages: [languages.harbour], format: FileFormat.svg },\n    { icon: 'haskell', extensions: [], languages: [languages.haskell, languages.literatehaskell], format: FileFormat.svg },\n    { icon: 'haskell2', extensions: [], languages: [languages.haskell, languages.literatehaskell], format: FileFormat.svg, disabled: true },\n    { icon: 'haxe', extensions: ['haxelib.json'], filename: true, languages: [languages.haxe], format: FileFormat.svg },\n    { icon: 'haxecheckstyle', extensions: ['checkstyle.json'], filename: true, format: FileFormat.svg },\n    { icon: 'haxedevelop', extensions: ['hxproj'], format: FileFormat.svg },\n    { icon: 'hlsl', extensions: [], languages: [languages.hlsl], format: FileFormat.svg },\n    { icon: 'html', extensions: [], languages: [languages.html], format: FileFormat.svg },\n    { icon: 'idris', extensions: ['idr', 'lidr'], format: FileFormat.svg },\n    { icon: 'idrisbin', extensions: ['ibc'], format: FileFormat.svg },\n    { icon: 'idrispkg', extensions: ['ipkg'], format: FileFormat.svg },\n    { icon: 'image', extensions: ['jpeg', 'jpg', 'gif', 'png', 'bmp', 'tiff', 'ico'], format: FileFormat.svg },\n    { icon: 'ini', extensions: [], languages: [languages.ini], light: true, format: FileFormat.svg },\n    { icon: 'infopath', extensions: ['infopathxml', 'xsn', 'xsf', 'xtp2'], format: FileFormat.svg },\n    { icon: 'ionic', extensions: ['ionic.project', 'ionic.config.json'], filename: true, format: FileFormat.svg },\n    { icon: 'jar', extensions: ['jar'], format: FileFormat.svg },\n    { icon: 'java', extensions: [], languages: [languages.java], format: FileFormat.svg },\n    { icon: 'jbuilder', extensions: ['jbuilder'], format: FileFormat.svg },\n    { icon: 'jenkins', extensions: ['jenkinsfile'], filename: true, format: FileFormat.svg },\n    { icon: 'jest', extensions: ['jest.config.js', 'jest.json', 'jest.config.json', '.jestrc'], filename: true, format: FileFormat.svg },\n    { icon: 'jinja', extensions: [], languages: [languages.jinja], format: FileFormat.svg },\n    { icon: 'js', extensions: [], light: true, languages: [languages.javascript], format: FileFormat.svg },\n    { icon: 'js_official', extensions: [], languages: [languages.javascript], format: FileFormat.svg, disabled: true },\n    { icon: 'jsconfig', extensions: ['jsconfig.json'], light: true, filename: true, format: FileFormat.svg },\n    { icon: 'jshint', extensions: ['.jshintrc', '.jshintignore'], filename: true, format: FileFormat.svg },\n    { icon: 'jsmap', extensions: ['js.map'], light: true, format: FileFormat.svg },\n    { icon: 'json', extensions: [], light: true, languages: [languages.json, languages.textmatejson], format: FileFormat.svg },\n    { icon: 'json_official', extensions: [], languages: [languages.json, languages.textmatejson], format: FileFormat.svg, disabled: true },\n    { icon: 'json2', extensions: [], languages: [languages.json, languages.textmatejson], format: FileFormat.svg, disabled: true },\n    { icon: 'jsonld', extensions: ['jsonld', 'json-ld'], light: true, format: FileFormat.svg },\n    { icon: 'jsp', extensions: ['jsp'], format: FileFormat.svg },\n    { icon: 'julia', extensions: [], languages: [languages.julia], format: FileFormat.svg },\n    { icon: 'julia2', extensions: [], languages: [languages.julia], format: FileFormat.svg, disabled: true },\n    { icon: 'karma', extensions: ['karma.conf.js', 'karma.conf.coffee'], filename: true, format: FileFormat.svg },\n    { icon: 'key', extensions: ['key', 'pem'], format: FileFormat.svg },\n    { icon: 'kite', extensions: ['.kiteignore'], light: true, filename: true, format: FileFormat.svg },\n    { icon: 'kitchenci', extensions: ['.kitchen.yml'], filename: true, format: FileFormat.svg },\n    { icon: 'kotlin', extensions: [], languages: [languages.kotlin], format: FileFormat.svg },\n    { icon: 'layout', extensions: ['master', 'layout.html', 'layout.htm'], format: FileFormat.svg },\n    { icon: 'layout', extensions: ['layout.html', 'layout.htm'], filename: true, format: FileFormat.svg },\n    { icon: 'lerna', extensions: ['lerna.json'], light: true, filename: true, format: FileFormat.svg },\n    { icon: 'less', extensions: [], languages: [languages.less], format: FileFormat.svg },\n    { icon: 'license', extensions: ['enc'], format: FileFormat.svg },\n    { icon: 'license', extensions: ['license', 'licence', 'license.md', 'licence.md', 'license.txt', 'licence.txt'], filename: true, format: FileFormat.svg },\n    { icon: 'lisp', extensions: [], languages: [languages.lisp], format: FileFormat.svg },\n    { icon: 'lime', extensions: ['hxp'], format: FileFormat.svg },\n    { icon: 'lime', extensions: ['include.xml'], filename: true, format: FileFormat.svg },\n    { icon: 'liquid', extensions: ['liquid'], format: FileFormat.svg },\n    { icon: 'locale', extensions: [], format: FileFormat.svg },\n    { icon: 'log', extensions: ['log'], format: FileFormat.svg },\n    { icon: 'lsl', extensions: ['lsl'], format: FileFormat.svg },\n    { icon: 'lua', extensions: [], languages: [languages.lua], format: FileFormat.svg },\n    { icon: 'lync', extensions: ['crec', 'ocrec'], format: FileFormat.svg },\n    { icon: 'makefile', extensions: ['makefile'], format: FileFormat.svg },\n    { icon: 'makefile', extensions: [], languages: [languages.makefile], format: FileFormat.svg },\n    { icon: 'map', extensions: ['map'], format: FileFormat.svg },\n    { icon: 'markdown', extensions: ['mdown', 'markdown'], languages: [languages.markdown], format: FileFormat.svg },\n    { icon: 'markdownlint', extensions: ['.markdownlint.json'], filename: true, format: FileFormat.svg },\n    { icon: 'marko', extensions: [], languages: [languages.marko], format: FileFormat.svg },\n    { icon: 'markojs', extensions: ['marko.js'], format: FileFormat.svg },\n    { icon: 'matlab', extensions: ['fig', 'mex', 'mexn', 'mexrs6', 'mn', 'mum', 'mx', 'mx3', 'rwd', 'slx', 'slddc', 'smv', 'tikz', 'xvc'], languages: [languages.matlab], format: FileFormat.png },\n    { icon: 'meteor', extensions: [], format: FileFormat.svg },\n    { icon: 'mjml', extensions: [], languages: [languages.mjml], format: FileFormat.svg },\n    { icon: 'mustache', extensions: ['mustache', 'mst'], light: true, format: FileFormat.svg },\n    { icon: 'nim', extensions: [], languages: [languages.nim], format: FileFormat.svg },\n    { icon: 'njsproj', extensions: ['njsproj'], format: FileFormat.svg },\n    { icon: 'node', extensions: ['.nvmrc'], filename: true, format: FileFormat.svg },\n    { icon: 'node2', extensions: [], format: FileFormat.svg, disabled: true },\n    { icon: 'npm', extensions: ['.npmignore', '.npmrc', 'package.json', 'package-lock.json'], filename: true, format: FileFormat.svg },\n    { icon: 'nsi', extensions: [], languages: [languages.nsis], format: FileFormat.svg },\n    { icon: 'nuget', extensions: ['nupkg', 'nuspec', 'psmdcp'], format: FileFormat.svg },\n    { icon: 'nunjucks', extensions: ['nunj', 'njs'], languages: [languages.nunjucks], format: FileFormat.svg },\n    { icon: 'objectivec', extensions: [], languages: [languages.objectivec], format: FileFormat.svg },\n    { icon: 'objectivecpp', extensions: [], languages: [languages.objectivecpp], format: FileFormat.svg },\n    { icon: 'ocaml', extensions: ['.merlin'], filename: true, languages: [languages.ocaml], format: FileFormat.svg },\n    { icon: 'onenote', extensions: ['one', 'onepkg', 'onetoc', 'onetoc2', 'sig'], format: FileFormat.svg },\n    { icon: 'opencl', extensions: ['cl', 'opencl'], format: FileFormat.svg },\n    { icon: 'outlook', extensions: ['pst', 'bcmx', 'otm', 'msg', 'oft'], format: FileFormat.svg },\n    { icon: 'package', extensions: ['pkg'], format: FileFormat.svg },\n    { icon: 'paket', extensions: ['paket.dependencies', 'paket.lock', 'paket.references', 'paket.template', 'paket.local'], filename: true, format: FileFormat.svg },\n    { icon: 'patch', extensions: ['patch'], format: FileFormat.svg },\n    { icon: 'pcl', extensions: ['pcd'], light: true, format: FileFormat.svg },\n    { icon: 'pdf', extensions: ['pdf'], format: FileFormat.svg },\n    { icon: 'pdf2', extensions: ['pdf'], format: FileFormat.svg, disabled: true },\n    { icon: 'perl', extensions: [], languages: [languages.perl], format: FileFormat.svg },\n    { icon: 'perl2', extensions: [], languages: [languages.perl], format: FileFormat.svg, disabled: true },\n    { icon: 'perl6', extensions: [], languages: [languages.perl6], format: FileFormat.svg },\n    { icon: 'photoshop', extensions: ['psd'], format: FileFormat.svg },\n    { icon: 'photoshop2', extensions: ['psd'], format: FileFormat.svg, disabled: true },\n    { icon: 'php', extensions: ['php1', 'php2', 'php3', 'php4', 'php5', 'php6', 'phps', 'phpsa', 'phpt', 'phtml', 'phar'], languages: [languages.php], format: FileFormat.svg },\n    { icon: 'php2', extensions: ['php1', 'php2', 'php3', 'php4', 'php5', 'php6', 'phps', 'phpsa', 'phpt', 'phtml', 'phar'], languages: [languages.php], format: FileFormat.svg, disabled: true },\n    { icon: 'php3', extensions: ['php1', 'php2', 'php3', 'php4', 'php5', 'php6', 'phps', 'phpsa', 'phpt', 'phtml', 'phar'], languages: [languages.php], format: FileFormat.svg, disabled: true },\n    { icon: 'phpunit', extensions: ['phpunit', 'phpunit.xml', 'phpunit.xml.dist'], filename: true, format: FileFormat.svg },\n    { icon: 'plantuml', extensions: ['pu', 'plantuml', 'iuml', 'puml'], format: FileFormat.svg },\n    { icon: 'plsql', extensions: [], languages: [languages.plsql], format: FileFormat.svg },\n    { icon: 'plsql_package', extensions: ['pck'], format: FileFormat.svg },\n    { icon: 'plsql_package_body', extensions: ['pkb'], format: FileFormat.svg },\n    { icon: 'plsql_package_header', extensions: ['pkh'], format: FileFormat.svg },\n    { icon: 'plsql_package_spec', extensions: ['pks'], format: FileFormat.svg },\n    { icon: 'poedit', extensions: ['po', 'mo'], format: FileFormat.svg },\n    { icon: 'polymer', extensions: [], languages: [languages.polymer], format: FileFormat.svg },\n    { icon: 'postcss', extensions: ['.postcssrc.js', 'postcss.config.js'], filename: true, languages: [languages.postcss], format: FileFormat.svg },\n    { icon: 'powerpoint', extensions: ['pot', 'potx', 'potm', 'pps', 'ppsx', 'ppsm', 'ppt', 'pptx', 'pptm', 'pa', 'ppa', 'ppam', 'sldm', 'sldx'], format: FileFormat.svg },\n    { icon: 'powershell', extensions: [], languages: [languages.powershell], format: FileFormat.svg },\n    { icon: 'procfile', extensions: ['procfile'], filename: true, format: FileFormat.svg },\n    { icon: 'progress', extensions: [], languages: [languages.openEdge], format: FileFormat.svg },\n    { icon: 'prolog', extensions: ['pro', 'P'], languages: [languages.prolog], format: FileFormat.svg },\n    { icon: 'protobuf', extensions: [], languages: [languages.protobuf], format: FileFormat.svg },\n    { icon: 'protractor', extensions: ['protractor.conf.js'], filename: true, format: FileFormat.svg },\n    { icon: 'publisher', extensions: ['pub', 'puz'], format: FileFormat.svg },\n    { icon: 'puppet', extensions: [], languages: [languages.puppet], format: FileFormat.svg },\n    { icon: 'pug', extensions: ['.jade-lintrc', '.pug-lintrc', '.jade-lint.json', '.pug-lintrc.js', '.pug-lintrc.json'], filename: true, languages: [languages.pug], format: FileFormat.svg },\n    { icon: 'purescript', extensions: [], light: true, languages: [languages.purescript], format: FileFormat.svg },\n    { icon: 'python', extensions: [], languages: [languages.python], format: FileFormat.svg },\n    { icon: 'qlikview', extensions: ['qvd', 'qvw'], languages: [languages.qlik], format: FileFormat.svg },\n    { icon: 'r', extensions: [], languages: [languages.r], format: FileFormat.svg },\n    { icon: 'rails', extensions: [], format: FileFormat.svg },\n    { icon: 'rake', extensions: ['rake'], format: FileFormat.svg },\n    { icon: 'rake', extensions: ['rakefile'], filename: true, format: FileFormat.svg },\n    { icon: 'raml', extensions: [], languages: [languages.raml], format: FileFormat.svg },\n    { icon: 'razor', extensions: [], languages: [languages.razor], format: FileFormat.svg },\n    { icon: 'reactjs', extensions: [], languages: [languages.javascriptreact], format: FileFormat.svg },\n    { icon: 'reacttemplate', extensions: ['rt'], format: FileFormat.svg },\n    { icon: 'reactts', extensions: [], languages: [languages.typescriptreact], format: FileFormat.svg },\n    { icon: 'reason', extensions: [], languages: [languages.reason], format: FileFormat.svg },\n    { icon: 'rest', extensions: [], languages: [languages.restructuredtext], format: FileFormat.svg },\n    { icon: 'registry', extensions: ['reg'], format: FileFormat.svg },\n    { icon: 'riot', extensions: [], languages: [languages.riot], format: FileFormat.svg },\n    { icon: 'robotframework', extensions: [], languages: [languages.robot], format: FileFormat.svg },\n    { icon: 'rollup', extensions: ['rollup.config.js', 'rollup.config.ts'], filename: true, format: FileFormat.svg },\n    { icon: 'rspec', extensions: ['.rspec'], filename: true, format: FileFormat.svg },\n    { icon: 'ruby', extensions: [], languages: [languages.ruby], format: FileFormat.svg },\n    { icon: 'rust', extensions: [], languages: [languages.rust], format: FileFormat.svg },\n    { icon: 'saltstack', extensions: ['sls'], format: FileFormat.svg },\n    { icon: 'sass', extensions: ['sass'], format: FileFormat.svg },\n    { icon: 'sbt', extensions: [], languages: [languages.sbt], format: FileFormat.svg },\n    { icon: 'scala', extensions: [], languages: [languages.scala], format: FileFormat.svg },\n    { icon: 'script', extensions: [], languages: [languages.vbscript], format: FileFormat.svg },\n    { icon: 'scss', extensions: ['scssm'], languages: [languages.scss], format: FileFormat.svg },\n    { icon: 'sequelize', extensions: ['.sequelizerc'], filename: true, format: FileFormat.svg },\n    { icon: 'shaderlab', extensions: [], languages: [languages.shaderlab], light: true, format: FileFormat.svg },\n    { icon: 'shell', extensions: ['fish'], languages: [languages.shellscript], format: FileFormat.svg },\n    { icon: 'slim', extensions: [], languages: [languages.slim], format: FileFormat.svg },\n    { icon: 'sln', extensions: ['sln'], format: FileFormat.svg },\n    { icon: 'smarty', extensions: [], languages: [languages.smarty], format: FileFormat.svg },\n    { icon: 'snyk', extensions: ['.snyk'], filename: true, format: FileFormat.svg },\n    { icon: 'solidity', extensions: [], light: true, languages: [languages.solidity], format: FileFormat.svg },\n    { icon: 'source', extensions: [], format: FileFormat.svg },\n    { icon: 'sqf', extensions: [], languages: [languages.sqf], format: FileFormat.svg },\n    { icon: 'sql', extensions: [], languages: [languages.sql], format: FileFormat.svg },\n    { icon: 'sqlite', extensions: ['sqlite', 'sqlite3', 'db3'], format: FileFormat.svg },\n    { icon: 'sss', extensions: ['sss'], format: FileFormat.svg },\n    { icon: 'style', extensions: [], format: FileFormat.svg },\n    { icon: 'stylelint', extensions: ['.stylelintrc', 'stylelint.config.js', '.stylelintignore'], light: true, filename: true, format: FileFormat.svg },\n    { icon: 'stylus', extensions: [], languages: [languages.stylus], format: FileFormat.svg },\n    { icon: 'storyboard', extensions: ['storyboard'], format: FileFormat.svg },\n    { icon: 'storybook', extensions: ['story.js', 'stories.js'], format: FileFormat.svg },\n    { icon: 'svg', extensions: ['svg'], format: FileFormat.svg },\n    { icon: 'swagger', extensions: [], languages: [languages.swagger], format: FileFormat.svg },\n    { icon: 'swift', extensions: ['package.pins'], filename: true, languages: [languages.swift], format: FileFormat.svg },\n    { icon: 'tcl', extensions: ['tcl', 'exp'], format: FileFormat.svg },\n    { icon: 'terraform', extensions: ['tfstate'], languages: [languages.terraform], format: FileFormat.svg },\n    { icon: 'test', extensions: ['tst'], format: FileFormat.svg },\n    { icon: 'testjs', extensions: ['test.js', 'spec.js', 'test.jsx', 'spec.jsx'], light: true, format: FileFormat.svg },\n    { icon: 'testts', extensions: ['test.ts', 'test.tsx', 'spec.ts', 'spec.tsx'], format: FileFormat.svg },\n    { icon: 'tex', extensions: ['texi'], languages: [languages.tex, languages.latex], light: true, format: FileFormat.svg },\n    { icon: 'text', extensions: ['csv'], languages: [languages.plaintext], format: FileFormat.svg },\n    { icon: 'textile', extensions: [], languages: [languages.textile], format: FileFormat.svg },\n    { icon: 'tfs', extensions: ['.tfignore'], filename: true, format: FileFormat.svg },\n    { icon: 'todo', extensions: ['todo'], light: true, format: FileFormat.svg },\n    { icon: 'toml', extensions: [], languages: [languages.toml], format: FileFormat.svg },\n    { icon: 'travis', extensions: ['.travis.yml'], filename: true, format: FileFormat.svg },\n    { icon: 'tsconfig', extensions: ['tsconfig.json', 'tsconfig.app.json', 'tsconfig.spec.json', 'tsconfig.e2e.json'], filename: true, format: FileFormat.svg },\n    { icon: 'tslint', extensions: ['tslint.json'], filename: true, format: FileFormat.svg },\n    { icon: 'twig', extensions: [], languages: [languages.twig], format: FileFormat.svg },\n    { icon: 'typescript', extensions: [], languages: [languages.typescript], format: FileFormat.svg },\n    { icon: 'typescript_official', extensions: [], languages: [languages.typescript], format: FileFormat.svg, disabled: true },\n    { icon: 'typescriptdef', extensions: ['d.ts'], format: FileFormat.svg },\n    { icon: 'typescriptdef_official', extensions: ['d.ts'], format: FileFormat.svg, disabled: true },\n    { icon: 'vagrant', extensions: ['vagrantfile'], filename: true, format: FileFormat.svg },\n    { icon: 'vash', extensions: ['vash'], light: true, format: FileFormat.svg },\n    { icon: 'vb', extensions: [], languages: [languages.vb], format: FileFormat.svg },\n    { icon: 'vbhtml', extensions: ['vbhtml'], format: FileFormat.svg },\n    { icon: 'vbproj', extensions: ['vbproj'], format: FileFormat.svg },\n    { icon: 'vcxproj', extensions: ['vcxproj'], format: FileFormat.svg },\n    { icon: 'vhdl', extensions: [], languages: [languages.vhdl], format: FileFormat.svg },\n    { // https://en.wikipedia.org/wiki/Video_file_format\n      icon: 'video',\n      extensions: ['3g2', '3gp', 'asf', 'amv', 'avi', 'divx', 'qt', 'f4a', 'f4b', 'f4p', 'f4v', 'flv',\n        'm2v', 'm4v', 'mkv', 'mk3d', 'mov', 'mp2', 'mp4', 'mpe', 'mpeg', 'mpeg2', 'mpg', 'mpv', 'nsv',\n        'ogv', 'rm', 'rmvb', 'svi', 'vob', 'webm', 'wmv'],\n      format: FileFormat.svg,\n    },\n    { icon: 'view', extensions: [], format: FileFormat.svg },\n    { icon: 'vim', extensions: ['.vimrc', '.gvimrc'], filename: true, languages: [languages.viml], format: FileFormat.svg },\n    { icon: 'volt', extensions: [], languages: [languages.volt], format: FileFormat.svg },\n    {\n      icon: 'vscode',\n      extensions: [\n        'vscodeignore.json',\n        'launch.json',\n        'tasks.json',\n        '.vscodeignore',\n      ],\n      filename: true,\n      format: FileFormat.svg,\n    },\n    { icon: 'vsix', extensions: ['vsix'], light: true, format: FileFormat.svg },\n    { icon: 'vue', extensions: [], languages: [languages.vue], format: FileFormat.svg },\n    { icon: 'watchmanconfig', extensions: ['.watchmanconfig'], filename: true, format: FileFormat.svg },\n    {\n      icon: 'webpack',\n      extensions: [\n        'webpack.base.conf.coffee',\n        'webpack.base.conf.js',\n        'webpack.base.conf.ts',\n        'webpack.common.coffee',\n        'webpack.common.js',\n        'webpack.common.ts',\n        'webpack.config.coffee',\n        'webpack.config.base.coffee',\n        'webpack.config.common.coffee',\n        'webpack.config.dev.coffee',\n        'webpack.config.development.coffee',\n        'webpack.config.staging.coffee',\n        'webpack.config.test.coffee',\n        'webpack.config.prod.coffee',\n        'webpack.config.production.coffee',\n        'webpack.config.js',\n        'webpack.config.base.js',\n        'webpack.config.common.js',\n        'webpack.config.dev.js',\n        'webpack.config.development.js',\n        'webpack.config.staging.js',\n        'webpack.config.test.js',\n        'webpack.config.prod.js',\n        'webpack.config.production.js',\n        'webpack.config.ts',\n        'webpack.config.base.ts',\n        'webpack.config.common.ts',\n        'webpack.config.dev.ts',\n        'webpack.config.development.ts',\n        'webpack.config.staging.ts',\n        'webpack.config.test.ts',\n        'webpack.config.prod.ts',\n        'webpack.config.production.ts',\n        'webpack.config.babel.js',\n        'webpack.config.base.babel.js',\n        'webpack.config.common.babel.js',\n        'webpack.config.dev.babel.js',\n        'webpack.config.development.babel.js',\n        'webpack.config.staging.babel.js',\n        'webpack.config.test.babel.js',\n        'webpack.config.prod.babel.js',\n        'webpack.config.production.babel.js',\n        'webpack.dev.coffee',\n        'webpack.dev.js',\n        'webpack.dev.ts',\n        'webpack.dev.conf.coffee',\n        'webpack.dev.conf.js',\n        'webpack.dev.conf.ts',\n        'webpack.prod.coffee',\n        'webpack.prod.js',\n        'webpack.prod.ts',\n        'webpack.prod.conf.coffee',\n        'webpack.prod.conf.js',\n        'webpack.prod.conf.ts',\n        'webpack.mix.coffee',\n        'webpack.mix.js',\n        'webpack.mix.ts',\n        'webpack.test.conf.coffee',\n        'webpack.test.conf.js',\n        'webpack.test.conf.ts',\n      ],\n      filename: true,\n      format: FileFormat.svg,\n    },\n    { icon: 'wercker', extensions: ['wercker.yml'], filename: true, format: FileFormat.svg },\n    { icon: 'word', extensions: ['doc', 'docx', 'docm', 'dot', 'dotx', 'dotm', 'wll'], format: FileFormat.svg },\n    { icon: 'wxml', extensions: ['wxml'], format: FileFormat.svg },\n    { icon: 'wxss', extensions: ['wxss'], format: FileFormat.svg },\n    { icon: 'xcode', extensions: ['xcodeproj'], format: FileFormat.svg },\n    { icon: 'xib', extensions: ['xib'], format: FileFormat.svg },\n    { icon: 'xliff', extensions: ['xliff', 'xlf'], format: FileFormat.svg },\n    { icon: 'xml', extensions: ['pex', 'tmlanguage'], languages: [languages.xml], format: FileFormat.svg },\n    { icon: 'xsl', extensions: [], languages: [languages.xsl], format: FileFormat.svg },\n    { icon: 'yaml', extensions: ['yml'], light: true, languages: [languages.yaml, languages.textmateyaml], format: FileFormat.svg },\n    { icon: 'yang', extensions: [], languages: [languages.yang], format: FileFormat.svg },\n    { icon: 'yarn', extensions: ['yarn.lock', '.yarnrc', '.yarnclean', '.yarn-integrity', '.yarn-metadata.json', '.yarnignore'], filename: true, format: FileFormat.svg },\n    { icon: 'yeoman', extensions: ['.yo-rc.json'], filename: true, format: FileFormat.svg },\n    { icon: 'zip', extensions: ['zip', 'rar', '7z', 'tar', 'gz', 'bzip2', 'xz', 'bz2'], format: FileFormat.svg },\n    { icon: 'zip2', extensions: ['zip', 'rar', '7z', 'tar', 'gz', 'bzip2', 'xz', 'bz2'], format: FileFormat.svg, disabled: true },\n  ],\n};\n"
  },
  {
    "path": "misc/init-demo.sh",
    "content": "#!/bin/bash\n\nrm -rf ~/demo\nmkdir -p ~/demo/mybooklive/Tv.Show\n\nfor i in {1..5}; do\n\tfolder=~/demo/downloads/Tv.Show.S01E0$i\n\tmkdir -p $folder\n\ttouch $folder/Tv.Show.S01E0$i.{mkv,srt,nfo}\ndone\n"
  },
  {
    "path": "misc/link-all.sh",
    "content": "#!/bin/bash\nnpm link jumpfm-clock jumpfm-copy jumpfm-fs jumpfm-gist jumpfm-git-status jumpfm-history jumpfm-jump jumpfm-version jumpfm-weather jumpfm-zip jumpfm-file-ops jumpfm-key-nav jumpfm-flat-mode \n"
  },
  {
    "path": "misc/modd.conf",
    "content": "icons.awk {\n\tprep: ./icons.awk icons.txt \n}\n"
  },
  {
    "path": "misc/omg-ubunut.txt",
    "content": "Hi Joey,\n\nI'm the developer of JumpFm (http://jumpfm.org).\nIts an Electron based (I know, I know.. but its really not that bad) file manager inspired by fman (http://bit.ly/2x70Q39) and exa (https://the.exa.website/).\n\nIt comes with some cool features like jumping, git status integration and more (see http://jumpfm.org for more details).\n\nIt is a completely open source project, and it is extremely easy (as easy as writing NPM package) to extend its functionality via its plug-in system. \n\nIt can be downloaded from github as an AppImage (https://github.com/Gilad-Kutiel-App/jumpfm/releases).\n\nI also made some short videos (http://bit.ly/2wljs2L).\n\nBest,\nGilad Kutiel\n\n"
  },
  {
    "path": "misc/reddit.txt",
    "content": "tl;dr\nI made JumpFm (http://bit.ly/2uBFWIC), need your feedback\n\nHi,\n\nI decided to format my machine last weekend. \nThen, after reinstalling almost everything, I was about to reinstall double commander.\nBasically, I like double commander but it takes me a lot of configuration to bring it to a state that I can work with it.\nI also came across fman (http://bit.ly/2uBwIw6) couple of weeks ago, and it seems really great but it is not an open source and requires a subscription fee.\nAlso, I haven't done any programming for quite a while and wanted to refresh my memory a bit.\nFor all the above reasons I decided to start a new side project and create my own file manager - JumpFm (http://bit.ly/2uBFWIC).\nIt is currently in a very early stage but there is already a working version. It was only tested on my machine but it seems to do what is suppose to do.\nI'm planning to keep working on this project and would really appreciate your feedback:\n- Which FM are you using ?\n- What features do you like/need/must ?\n- Feedback about the site/help/etc...\n- Bugs\n\nThank you very much,\nGilad\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"jumpfm\",\n    \"version\": \"1.0.6\",\n    \"description\": \"A file manager that lets you jump\",\n    \"author\": {\n        \"name\": \"Gilad Kutiel\",\n        \"email\": \"gilad.kutiel@gmail.com\"\n    },\n    \"license\": \"ISC\",\n    \"dependencies\": {\n        \"check-dependencies\": \"^1.1.0\",\n        \"electron-simple-updater\": \"^1.2.1\",\n        \"filesize\": \"^3.5.10\",\n        \"fs-extra\": \"^4.0.1\",\n        \"homedir\": \"^0.6.0\",\n        \"moment\": \"^2.18.1\",\n        \"node-cmd\": \"^3.0.0\",\n        \"node-watch\": \"^0.5.5\",\n        \"node.os\": \"^1.2.4\",\n        \"npm\": \"^6.2.0\"\n    },\n    \"devDependencies\": {\n        \"@types/electron\": \"^1.6.10\",\n        \"@types/fs-extra\": \"^4.0.8\",\n        \"@types/keyboardjs\": \"^2.2.31\",\n        \"@types/node\": \"^8.10.21\",\n        \"@types/npm\": \"^2.0.29\",\n        \"@types/watch\": \"^1.0.0\",\n        \"electron-builder\": \"^19.56.2\",\n        \"jumpfm-api\": \"^1.1.4\"\n    },\n    \"optionalDependencies\": {\n        \"7zip-bin-win\": \"^2.1.0\"\n    },\n    \"homepage\": \"https://jumpfm.org\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git+https://github.com/Gilad-Kutiel-App/jumpfm.git\"\n    },\n    \"bugs\": {\n        \"url\": \"https://github.com/Gilad-Kutiel-App/jumpfm/issues\"\n    },\n    \"main\": \"app.js\",\n    \"build\": {\n        \"appId\": \"com.github.gkutiel.jumpfm\",\n        \"compression\": \"maximum\",\n        \"electronVersion\": \"1.7.5\",\n        \"linux\": {\n            \"files\": [\n                \"**/*\",\n                \"build/icons/*\"\n            ],\n            \"target\": [\n                \"AppImage\"\n            ],\n            \"category\": \"Utility\",\n            \"publish\": \"github\"\n        },\n        \"win\": {\n            \"target\": \"nsis\"\n        }\n    },\n    \"scripts\": {\n        \"test\": \"mocha js\",\n        \"pack\": \"build --dir\",\n        \"dist\": \"build\",\n        \"publish\": \"build --publish always\",\n        \"postinstall\": \"electron-builder install-app-deps\"\n    }\n}\n"
  },
  {
    "path": "scss/_app.scss",
    "content": "@import 'colors';\n@font-face {\n    font-family: DroidSansMono;\n    src: url('../fonts/DroidSansMono.ttf');\n}\n\n* {\n    font-family: 'DroidSansMono';\n    box-sizing: border-box;\n}\n\nhtml,\nbody {\n    height: 100%;\n    width: 100%;\n    padding: 0;\n    margin: 0;\n}\n\n#app {\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    left: 0;\n    right: 0;\n    display: flex;\n    flex-direction: column;\n}\n\nhtml,\nbody,\ntable,\ninput {\n    background-color: $color-bg;\n    color: $color-fg;\n}\n\ninput {\n    font-size: inherit;\n    padding: .2em;\n    outline: none;\n    border: none;\n    background-color: $color-bg;\n}"
  },
  {
    "path": "scss/_classes.scss",
    "content": "@import 'colors';\n.border {\n  border: 1px solid $color-natural;\n}\n\n.txt-no-wrap {\n  white-space: nowrap;\n  text-overflow: ellipsis;\n  overflow: hidden;\n}\n\n ::-webkit-scrollbar {\n  display: none;\n}\n\n.file-icon {\n  width: 1em;\n  height: 1em;\n}"
  },
  {
    "path": "scss/_colors.scss",
    "content": "$color-border:#646464;\n$color-err:#F58E8E;\n$color-natural:#A9D3AB;\n$color-warn:#FED37E;\n$color-info:#7AABD4;\n$color-06:#D6ADD5;\n$color-07:#79D4D5;\n$color-08:#D4D4D4;\n$color-bg:lighten(#2D2D2D, 5%);\n$color-fg:#D4D4D4;"
  },
  {
    "path": "scss/_dialog.scss",
    "content": "@import 'colors';\n@import 'classes';\n#dialog {\n  display: none;\n  position: absolute;\n  top: 20%;\n  left: 0;\n  right: 0;\n  width: 600px;\n  margin: 0 auto;\n  background-color: transparent;\n}\n\n#dialog-label,\n#dialog-input {\n  padding: .8em;\n}\n\n#dialog-label,\n#dialog-input,\n#dialog-sug {\n  @extend .border;\n  background-color: $color-bg;\n}\n\n#dialog-input,\n#dialog-sug {\n  width: 100%;\n}\n\n#dialog-content {\n  display: flex;\n  #dialog-label {\n    color: $color-info;\n    font-weight: bold;\n    border-right: none;\n  }\n}\n\n#dialog-left {\n  flex-grow: 0;\n  flex-shrink: 0;\n}\n\n#dialog-right {\n  flex-grow: 1;\n  overflow: hidden;\n  #dialog-input {\n    border-left: none;\n    padding-left: 0;\n  }\n  #dialog-sug {\n    border-top: none;\n    line-height: 1.5;\n    padding: 0;\n    list-style-type: none;\n    margin: 0;\n    [cur] {\n      background-color: $color-border;\n    }\n    b {\n      color: $color-info;\n    }\n    li {\n      @extend .txt-no-wrap;\n      padding: 0 .2em;\n    }\n  }\n}"
  },
  {
    "path": "scss/_panel.scss",
    "content": "@import 'colors';\n@import 'classes';\n.panel {\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  width: 50%;\n  border: 0 solid $color-border;\n  &:first-child {\n    border-right-width: 2px\n  }\n  &:nth-child(2) {\n    border-left-width: 2px\n  }\n}\n\n.panel>title {\n  @extend .txt-no-wrap;\n  flex-shrink: 0;\n  display: block;\n  width: 100%;\n}\n\n.panel[active]>title {\n  background-color: darken($color-info, 20%);\n  font-weight: bold;\n}\n\n.panel tr[hidden] {\n  display: none;\n}\n\n.panel tr[cur] {\n  background-color: $color-border;\n  font-weight: bold;\n}\n\n.panel[active] tr[selected] {\n  background-color: darken($color-warn, 55%);\n}\n\n.panel[active] tr[cur] {\n  background-color: darken($color-warn, 45%);\n}\n\ntable {\n  width: 100%;\n  display: flex;\n  flex-direction: column;\n  table-layout: fixed;\n  flex-grow: 1;\n  border-collapse: collapse;\n  text-align: left;\n}\n\nthead {\n  background-image: linear-gradient($color-border, $color-bg);\n}\n\ntbody {\n  display: block;\n  overflow-y: auto;\n  flex-grow: 1;\n}\n\nthead,\ntbody tr {\n  display: table;\n  width: 100%;\n  table-layout: fixed;\n}\n\nth:first-child,\ntd:first-child {\n  width: 1.5em;\n}\n\nth:nth-child(3),\ntd:nth-child(3) {\n  width: 6em;\n}\n\nth:nth-child(4),\ntd:nth-child(4) {\n  width: 10.5em;\n}\n\nth,\ntd {\n  @extend .txt-no-wrap;\n  padding: .1em .2em;\n  &:first-child {\n    padding-left: .5em;\n    padding-right: 1.5em;\n  }\n  &:last-child {\n    padding-right: .5em;\n  }\n}\n\n.filter {\n  border-top: 1px solid $color-border;\n}"
  },
  {
    "path": "scss/_panels.scss",
    "content": "#panels {\n    flex-grow: 1;\n    display: flex;\n    flex-direction: row;\n}"
  },
  {
    "path": "scss/_statusbar.scss",
    "content": "@import 'colors';\n// \n#statusbar {\n    display: flex;\n    flex-shrink: 0;\n    padding: .1em 0;\n    border-top: 3px solid $color-border;\n}\n\n#statusbar-msgs {\n    display: flex;\n    flex-grow: 1;\n    align-items: center;\n    font-size: 90%;\n    padding-right: 1em;\n    overflow: hidden;\n    .msg {\n        cursor: default;\n        flex-shrink: 0;\n        border-right: 1px solid $color-border;\n        padding: 0 .5em;\n        &:last-child {\n            border-right: none;\n        }\n        &:empty {\n            padding: 0;\n        }\n    }\n}\n\n#statusbar-buttons {\n    display: flex;\n    flex-direction: row-reverse;\n    flex-shrink: 0;\n    font-size: 120%;\n    .btn {\n        padding-right: .5em;\n    }\n}\n\n.btn {\n    font-size: 1em;\n    display: inline-block;\n    cursor: pointer;\n    padding: 0;\n    margin: 0;\n    color: $color-natural;\n    border: none;\n    outline: none;\n    opacity: .5;\n    text-decoration: none;\n    &:hover {\n        opacity: 1;\n    }\n}\n\n[type=info],\n[type=info][data-title]:before {\n    color: $color-info;\n}\n\n[type=warn],\n[type=warn][data-title]:before {\n    color: $color-warn;\n}\n\n[type=err],\n[type=err][data-title]:before {\n    color: $color-err;\n}"
  },
  {
    "path": "scss/_tooltip.scss",
    "content": "@import 'colors';\n[data-title] {\n    &:before {\n        position: absolute;\n        content: attr(data-title);\n        white-space: nowrap;\n        color: $color-fg;\n        background-color: $color-bg;\n        width: auto;\n        padding: .5em;\n        border: 1px solid $color-border;\n        border-radius: 3px;\n        transform: translate(1em, -3.5em);\n        opacity: 0;\n        visibility: hidden;\n    }\n    &:hover:before {\n        visibility: visible;\n        transition: all .2s;\n        opacity: 1;\n        height: auto;\n    }\n}\n\n#statusbar-buttons {\n    [data-title] {\n        &:before {\n            font-size: 80%;\n            transform: translate(-3em, -3em);\n        }\n        &:hover:before {}\n    }\n}"
  },
  {
    "path": "scss/index.scss",
    "content": "@import 'app';\n@import 'panels';\n@import 'panel';\n@import 'classes';\n@import 'dialog';\n@import 'statusbar';\n@import 'tooltip';"
  },
  {
    "path": "ts/Dialog.ts",
    "content": "import { Dialog as DialogApi, DialogSpec, Suggestion } from 'jumpfm-api'\nimport { shortway } from \"./shortway\";\n\ninterface SuggestionLi extends Suggestion {\n    li: HTMLLIElement\n}\n\nexport class Dialog implements DialogApi {\n    private readonly divDialog: HTMLDivElement =\n    document.getElementById('dialog') as HTMLDivElement\n\n    private readonly divLabel: HTMLDivElement =\n    document.getElementById('dialog-label') as HTMLDivElement\n\n    private readonly input: HTMLInputElement =\n    document.getElementById('dialog-input') as HTMLInputElement\n\n    private readonly olSug: HTMLOListElement =\n    document.getElementById('dialog-sug') as HTMLOListElement\n\n    private suggestions: SuggestionLi[] = []\n\n    private onAccept = (val: string, sug: Suggestion) => { }\n    private suggest = val => []\n    private cur: number\n\n    constructor() {\n        const on = (type, action, capture = false) =>\n            this.input.addEventListener(type, action, capture)\n\n        on('keydown', e => {\n            e.stopPropagation()\n        })\n        on('blur', this.close)\n        on('keydown', shortway('esc', this.close))\n        on('keydown', shortway('enter', () => {\n            this.close()\n            this.onAccept(\n                this.input.value,\n                this.suggestions[this.cur]\n            )\n        }))\n\n\n        on('keydown', shortway('down', () =>\n            this.setCur(this.cur + 1)\n        ))\n\n        on('keydown', shortway('up', () =>\n            this.setCur(this.cur - 1)\n        ))\n\n        on('input', this.updateSuggestions)\n    }\n\n    private updateSuggestions = () => {\n        this.suggestions =\n            this.suggest(this.input.value)\n                .map(sug => {\n                    const li = document.createElement('li')\n                    li.innerHTML = sug.html\n                    sug.li = li\n                    return sug\n                })\n        this.clearSuggestions()\n        this.suggestions.forEach(sug => {\n            this.olSug.appendChild(sug.li)\n        })\n        const curSuggestion = this.suggestions[this.cur = 0]\n        if (curSuggestion) curSuggestion.li.setAttribute('cur', '')\n    }\n\n    private setCur = (i: number) => {\n        if (!this.suggestions.length) return\n        const curSug = this.suggestions[this.cur]\n        if (curSug) curSug.li.removeAttribute('cur')\n        this.cur = Math.max(0, Math.min(i, this.suggestions.length - 1))\n        this.suggestions[this.cur].li.setAttribute('cur', '')\n    }\n\n    private clearSuggestions = () => {\n        while (this.olSug.lastChild) this.olSug.removeChild(this.olSug.lastChild)\n    }\n\n\n    private close = () => {\n        this.divDialog.style.display = 'none'\n    }\n\n    open = (spec: DialogSpec) => {\n        this.suggestions = []\n        this.clearSuggestions()\n        this.divLabel.textContent = spec.label\n        this.cur = 0\n\n        this.divDialog.style.display = 'block'\n        this.input.value = ''\n        this.input.select()\n\n        spec.onOpen && spec.onOpen(this.input)\n        this.suggest = spec.suggest || (val => [])\n        this.onAccept = spec.onAccept\n    }\n}"
  },
  {
    "path": "ts/Filter.ts",
    "content": "import { FilterBox as FilterApi } from 'jumpfm-api'\nimport { getKeys } from \"./files\";\nimport { shortway } from \"./shortway\";\n\nexport class Filter implements FilterApi {\n    readonly input: HTMLInputElement = document.createElement('input')\n\n    private readonly handlers: ((val: string) => void)[] = []\n\n\n    constructor() {\n        this.input.className = 'filter'\n        this.input.addEventListener('keydown', e => e.stopPropagation(), false)\n        this.input.addEventListener('input', () => {\n            this.notifyAll()\n        }, false)\n\n        this.input.addEventListener('blur', this.hide, false)\n        this.hide()\n    }\n\n    private notifyAll(): any {\n        this.handlers.forEach(handler => {\n            handler(this.input.value)\n        });\n    }\n\n    set = (val: string) => {\n        this.input.value = val\n        this.notifyAll()\n    }\n\n    get = () => {\n        return this.input.value\n    }\n\n    onChange = (handler: (val: string) => void) => this.handlers.push(handler)\n\n    focus = () => {\n        this.input.style.display = 'block'\n        this.input.focus()\n    }\n\n    hide = () => this.input.style.display = 'none'\n\n    bind = (actionName: string, defaultKeys: string[], action: () => void) => {\n        getKeys(actionName, defaultKeys).forEach(combo => {\n            const cb = shortway(combo, (e) => {\n                e.preventDefault()\n                action()\n            })\n            this.input.addEventListener('keydown', cb, false)\n        })\n    }\n}"
  },
  {
    "path": "ts/Item.ts",
    "content": "import { Item as ItemApi, File } from 'jumpfm-api'\n\nimport * as moment from 'moment'\nimport * as fileSize from 'filesize'\n\ntype attr = 'cur' | 'hidden' | 'selected'\n\nexport class Item implements ItemApi {\n    private hidden: boolean = false\n    private selected: boolean = false\n\n    private readonly icon = document.createElement('img')\n\n    private readonly tdIcon = document.createElement('td')\n    private readonly tdName = document.createElement('td')\n    private readonly tdSize = document.createElement('td')\n    private readonly tdTime = document.createElement('td')\n\n    readonly tr = document.createElement('tr')\n\n    constructor(item: File) {\n        this.icon.className = 'file-icon'\n        this.tdIcon.appendChild(this.icon)\n\n        this.tr.appendChild(this.tdIcon)\n        this.tr.appendChild(this.tdName)\n        this.tr.appendChild(this.tdSize)\n        this.tr.appendChild(this.tdTime)\n\n        this.path = item.path\n        this.name = item.name\n\n        this.tdName.textContent =\n            item.name || '--'\n    }\n\n    readonly path: string;\n    readonly name: string;\n\n    private readonly attrs: { [attr: string]: boolean } = {}\n\n    private set = (attr: attr) => (b: boolean) => {\n        this.attrs[attr] = b\n        if (b)\n            this.tr.setAttribute(attr, '')\n        else\n            this.tr.removeAttribute(attr)\n        return this\n    }\n\n    private is = (attr: attr) => this.attrs[attr]\n\n    setCur: (b: boolean) => void = this.set('cur')\n    setHidden: (b: boolean) => void = this.set('hidden')\n    setSelected: (b: boolean) => void = this.set('selected')\n\n    hide = () => this.setHidden(true)\n    show = () => this.setHidden(false)\n\n    isSelected = () => this.is('selected')\n    isHidden = () => this.is('hidden')\n\n    setAttribute(name: string, val: string = '') {\n        this.tr.setAttribute(name, val)\n        return this\n    }\n\n    setIcon = (src: string) => {\n        this.icon.src = src\n        return this\n    }\n\n    setTime = (time: number) => {\n        this.tdTime.textContent =\n            (time && moment(time).format('DD/MM/YYYY hh:mm') || '--')\n        return this\n    }\n\n    setSize = (size: number) => {\n        this.tdSize.textContent =\n            (size && fileSize(size) || '--')\n        return this\n    }\n}"
  },
  {
    "path": "ts/JumpFm.ts",
    "content": "import { } from 'electron'\nimport { JumpFm as JumpFmApi } from 'jumpfm-api'\nimport { Panel } from \"./Panel\"\nimport { StatusBar } from \"./StatusBar\"\nimport { PluginManager } from \"./PluginManager\";\nimport { Dialog } from \"./Dialog\";\nimport { shortway } from \"./shortway\";\nimport { Settings } from \"./Settings\";\nimport {\n    getKeys\n    , saveKeyboard\n    , root\n    , packageJson\n    , keyboardPath\n    , settingsPath\n    , pluginsPackageJson\n} from \"./files\";\n\nimport * as homedir from 'homedir'\nimport * as fs from 'fs'\nimport * as watch from 'node-watch'\n\nexport class JumpFm implements JumpFmApi {\n    private active: 0 | 1 = 0\n    private readonly divPanels = document.getElementById('panels')\n    private readonly pluginManager = new PluginManager(this)\n    private readonly watchers: { [name: string]: fs.FSWatcher } = {}\n\n    readonly package = packageJson\n    readonly root = root\n    readonly settings = new Settings()\n    readonly dialog = new Dialog()\n    readonly electron: Electron.AllElectron = require('electron')\n    readonly panels: Panel[] = [new Panel(), new Panel()]\n    readonly statusBar: StatusBar = new StatusBar()\n    readonly argv: string[]\n\n    private passive = (): 0 | 1 => (this.active + 1) % 2 as 0 | 1\n\n    private setActive = (i: 0 | 1) => {\n        this.active = i\n        this.panels[this.active].setActive(true)\n        this.panels[this.passive()].setActive(false)\n    }\n\n    watchStart = (name, path, then, recursive = false) => {\n        this.watchStop(name)\n        console.log('WATCH START', name, path)\n        setImmediate(() => {\n            let to\n            this.watchers[name] = watch(path, { recursive: recursive }, () => {\n                clearTimeout(to)\n                to = setTimeout(then, 10)\n            })\n        })\n    }\n\n    watchStop = (name: string) => {\n        if (this.watchers[name]) {\n            console.log('WATCH STOP', name)\n            this.watchers[name].close()\n        }\n    }\n\n    getPanelActive = () =>\n        this.panels[this.active]\n\n    getPanelPassive = () =>\n        this.panels[this.passive()]\n\n    panelsSwap = () => {\n        this.active = this.passive()\n        const tmp = this.panels[0]\n        this.panels[0] = this.panels[1]\n        this.panels[1] = tmp\n        this.divPanels.insertBefore(this.panels[0].divPanel, this.panels[1].divPanel)\n    }\n\n    panelsSwitch = () =>\n        this.setActive(this.passive())\n\n    bind = (actionName: string, defaultKeys: string[], action: () => void) => {\n        getKeys(actionName, defaultKeys).forEach(combo => {\n            const cb = shortway(combo, (e) => {\n                e.preventDefault()\n                action()\n            })\n            document.addEventListener('keydown', cb, false)\n        })\n    }\n\n    constructor(argv: string[]) {\n        this.argv = argv\n        this.panels.forEach(panel => {\n            this.divPanels.appendChild(panel.divPanel)\n        })\n\n        const opn = (url) => () => this.electron.shell.openItem(url)\n        this.statusBar.buttonAdd('fa-info', 'About', opn('http://jumpfm.org'))\n        this.statusBar.buttonAdd('fa-key', 'Keyboard', opn(keyboardPath))\n        this.statusBar.buttonAdd('fa-gear', 'Settings', opn(settingsPath))\n        this.statusBar.buttonAdd('fa-plug', 'Plugins', opn(pluginsPackageJson))\n\n        this.pluginManager.loadAndUpdatePlugins(() => {\n            saveKeyboard()\n            this.panels.forEach(panel => panel.cd(homedir()))\n            this.setActive(0)\n        })\n    }\n}"
  },
  {
    "path": "ts/Panel.ts",
    "content": "import { Panel as PanelApi, Url, File } from 'jumpfm-api'\n\nimport { Item } from './Item'\nimport { Filter } from './Filter'\nimport { getKeys } from \"./files\";\nimport { shortway } from \"./shortway\";\n\nexport class Panel implements PanelApi {\n    private readonly table: HTMLTableElement = document.createElement('table')\n    private readonly tbody: HTMLTableSectionElement = document.createElement('tbody')\n    private readonly thead: HTMLTableSectionElement = document.createElement('thead')\n    private readonly title: HTMLTitleElement = document.createElement('title')\n    private readonly trHead: HTMLTableRowElement = document.createElement('tr')\n\n    readonly divPanel: HTMLDivElement = document.createElement('div')\n    readonly filterBox: Filter = new Filter()\n\n    private readonly filters: { [name: string]: ((item: Item) => boolean) } = {}\n    private readonly onCds: (() => void)[] = []\n    private readonly onItemsAddeds: ((newItems: Item[]) => void)[] = []\n    private readonly onLoads: (() => void)[] = []\n\n    private active = false\n    private url: Url\n    private cur: number = 0\n    private items: Item[] = []\n    private visibleItems: Item[] = []\n\n    constructor() {\n        ['', 'Name', 'Size', 'Time'].forEach(head => {\n            const td = document.createElement('td')\n            td.textContent = head\n            this.trHead.appendChild(td)\n        })\n\n        this.divPanel.className = 'panel'\n        this.divPanel.appendChild(this.title)\n        this.divPanel.appendChild(this.table)\n        this.thead.appendChild(this.trHead)\n        this.table.appendChild(this.thead)\n        this.table.appendChild(this.tbody)\n        this.divPanel.appendChild(this.filterBox.input)\n\n        this.filterBox.onChange(this.setTitle)\n    }\n\n    private clearItems = () => {\n        while (this.tbody.lastChild) this.tbody.removeChild(this.tbody.lastChild)\n    }\n\n    private addItems = (items: Item[]) => {\n        items.forEach(item => this.tbody.appendChild(item.tr))\n    }\n\n    private setTitle = () => {\n        const filter = this.filterBox.get()\n        const protocol = this.url.protocol\n        this.title.textContent =\n            (protocol ? protocol + ':' : '')\n            + this.url.path\n            + (filter ? ' [' + filter + ']' : '')\n    }\n\n    private scrollToCur = () => {\n        const curItem = this.getCurrentItem()\n        if (!curItem) return\n        const tr = curItem.tr\n        const trRect = tr.getBoundingClientRect()\n        const tbodyRect = this.tbody.getBoundingClientRect()\n        if (trRect.bottom > tbodyRect.bottom)\n            tr.scrollIntoView(false)\n        if (trRect.top < tbodyRect.top)\n            tr.scrollIntoView(true)\n    }\n\n    private safeUpdateCurrent = (b: boolean) => {\n        const item = this.getCurrentItem()\n        if (item) item.setCur(b)\n    }\n\n    private setCur = (i) => {\n        this.safeUpdateCurrent(false)\n        this.cur = Math.max(0, Math.min(i, this.visibleItems.length - 1))\n        this.safeUpdateCurrent(true)\n    }\n\n    private progressiveProcessItems =\n    (process: (items: Item[]) => void) =>\n        (from: number, done?: () => void) => {\n\n            if (from > this.items.length) return done && done()\n            const to = from + 100\n\n            process(this.items.slice(from, to))\n\n            setImmediate(() => {\n                this.progressiveProcessItems(process)(to, done)\n            })\n        }\n\n    private progressiveAddItems = this.progressiveProcessItems(items => {\n        this.addItems(items)\n\n        this.onItemsAddeds.forEach(f =>\n            setImmediate(() => f(items))\n        )\n    })\n\n    private progressiveUpdateVisibility = this.progressiveProcessItems(items => {\n        items.forEach(item => {\n            const visible =\n                Object.values(this.filters)\n                    .every(filter => filter(item))\n\n            if (visible) {\n                this.visibleItems.push(item)\n                item.show()\n            } else {\n                item.hide()\n            }\n        })\n    })\n\n\n    private updateVisibility = () => {\n        console.log('updateVisibility')\n        this.safeUpdateCurrent(false)\n        this.visibleItems = []\n        this.progressiveUpdateVisibility(0, () => {\n            console.log('progressiveUpdateVisibility')\n            this.setCur(this.cur)\n            this.scrollToCur()\n        })\n    }\n\n    private selectRange = (from: number, to: number) => {\n        if (from > to) return this.selectRange(to, from)\n        for (let i = from\n            ; i <= Math.min(to, this.visibleItems.length - 1)\n            ; i++\n        ) {\n            const item = this.visibleItems[i]\n            if (item) item.setSelected(true)\n        }\n    }\n\n    private rowsInView = () => {\n        return Math.floor(this.tbody.clientHeight / this.visibleItems[0].tr.scrollHeight)\n    }\n\n    private stepTo = (i, select: boolean = false) => {\n        if (select) this.selectRange(this.cur, i)\n        this.setCur(i)\n        this.scrollToCur()\n    }\n\n    setActive = (b: boolean) => {\n        this.active = b\n        if (b)\n            this.divPanel.setAttribute('active', '')\n        else\n            this.divPanel.removeAttribute('active')\n    }\n\n    cd(path: string): void;\n    cd(url: Url): void;\n    cd(pathOrUrl: any) {\n        if (typeof pathOrUrl == 'string') return this.cd({\n            protocol: '',\n            path: pathOrUrl,\n            query: {}\n        })\n\n        this.url = pathOrUrl as Url\n        console.log('cd', this.url)\n        this.setTitle()\n        this.onCds.forEach(f => setImmediate(f))\n    }\n\n    onCd = (then: () => void) =>\n        this.onCds.push(then)\n\n    onItemsAdded = (then: (newItems: Item[]) => void) =>\n        this.onItemsAddeds.push(then)\n\n    onLoad = (then: () => void) => {\n        this.onLoads.push(then)\n    }\n\n    step = (d: number, select?: boolean) => {\n        const newCur\n            = d < 0\n                ? Math.max(0, this.cur + d)\n                : Math.min(this.cur + d, this.visibleItems.length - 1)\n\n        this.stepTo(newCur, select)\n    }\n\n    stepPgUp = (select?: boolean) => {\n        this.step(-this.rowsInView(), select)\n    }\n\n    stepPgDown = (select?: boolean) => {\n        this.step(this.rowsInView(), select)\n    }\n\n    stepStart = (select?: boolean) => {\n        this.stepTo(0, select)\n    }\n\n    stepEnd = (select?: boolean) => {\n        this.stepTo(this.visibleItems.length - 1, select)\n    }\n\n    selectNone = () =>\n        this.items.forEach(item => item.setSelected(false))\n\n    selectAll = () =>\n        this.items.forEach(item => item.setSelected(true))\n\n    selectToggleCurrent = () => {\n        const item = this.getCurrentItem()\n        item.setSelected(!item.isSelected())\n    }\n\n    getUrl = () =>\n        this.url\n\n    getItems = () =>\n        this.items\n\n    getSelectedItems = () =>\n        this.visibleItems.filter((item, i) =>\n            i === this.cur || item.isSelected()\n        )\n\n    getCurrentItem = () =>\n        this.visibleItems[this.cur]\n\n    setItems = (items: File[]) => {\n        console.log('setItems')\n        this.items = items.map(item => new Item(item))\n\n        this.clearItems()\n        this.progressiveAddItems(0, () => {\n            this.onLoads.forEach(onLoad => {\n                setImmediate(onLoad)\n            })\n        })\n        this.updateVisibility()\n        return this\n    }\n\n    filterSet = (name: string, filter: (item: Item) => boolean) => {\n        console.log('filterSet', filter)\n        this.filters[name] = filter\n        this.updateVisibility()\n    }\n\n    filterRemove = (name: string) => {\n        delete this.filters[name]\n        this.updateVisibility()\n    }\n\n    bind = (actionName: string, defaultKeys: string[], action: () => void) => {\n        getKeys(actionName, defaultKeys).forEach(combo => {\n            const cb = shortway(combo, (e) => {\n                if (!this.active) return\n                e.preventDefault()\n                action()\n            })\n            document.addEventListener('keydown', cb, false)\n        })\n    }\n}"
  },
  {
    "path": "ts/PluginManager.ts",
    "content": "import { JumpFm } from 'jumpfm-api'\n\nimport {\n    pluginsPath,\n    pluginsPackageJson,\n    packageJson,\n    savePlugins\n} from './files';\n\nimport * as check from 'check-dependencies'\nimport * as fs from 'fs-extra'\nimport * as npm from 'npm'\nimport * as path from 'path'\nimport * as watch from 'node-watch'\n\ninterface checkRes {\n    status: number,         // 0 if successful, 1 otherwise \n    depsWereOk: boolean,    // true if dependencies were already satisfied \n    log: string[],          // array of logged messages \n    error: string[],        // array of logged errors \n}\n\nconst defaultPlugins = {\n    dependencies: {\n        \"jumpfm-font-size\": \"latest\",\n        \"jumpfm-clock\": \"latest\",\n        \"jumpfm-copy\": \"latest\",\n        \"jumpfm-file-ops\": \"latest\",\n        \"jumpfm-filter\": \"latest\",\n        \"jumpfm-flat-mode\": \"latest\",\n        \"jumpfm-fs\": \"latest\",\n        \"jumpfm-gist\": \"latest\",\n        \"jumpfm-git-status\": \"latest\",\n        \"jumpfm-history\": \"latest\",\n        \"jumpfm-icons\": \"latest\",\n        \"jumpfm-jump\": \"latest\",\n        \"jumpfm-key-nav\": \"latest\",\n        \"jumpfm-version\": \"latest\",\n        \"jumpfm-weather\": \"latest\",\n        \"jumpfm-zip\": \"latest\"\n    }\n}\n\nclass PluginsLoader {\n    jumpFm: JumpFm;\n    done: (err?: any) => void;\n    private loaded = {}\n\n    constructor(jumpFm: JumpFm, done: (err?) => void) {\n        this.jumpFm = jumpFm\n        this.done = () => {\n            jumpFm.statusBar.msg('plugins').setText('')\n            done()\n        }\n    }\n\n    private loadCss = (href) => {\n        if (!href) return\n        const link = document.createElement('link')\n        link.setAttribute('rel', 'stylesheet')\n        link.setAttribute('href', href)\n        document.head.appendChild(link)\n    }\n\n    private loadPlugin = (name: string) => {\n        try {\n            console.time(name)\n\n            if (this.loaded[name]) return\n            this.loaded[name] = true\n\n            const pluginDir = path.join(pluginsPath, 'node_modules', name)\n            const plugin = require(pluginDir)\n\n            if (plugin.css)\n                plugin.css.forEach(css =>\n                    this.loadCss(path.join(pluginDir, css))\n                )\n\n            plugin.load(this.jumpFm)\n\n            console.timeEnd(name)\n        } catch (e) {\n            console.log(e)\n        }\n    }\n\n    private getPackage = () => {\n        try {\n            return fs.readJsonSync(pluginsPackageJson)\n        } catch (e) {\n            fs.writeFileSync(\n                pluginsPackageJson\n                , JSON.stringify(defaultPlugins, null, 4)\n            )\n            return defaultPlugins\n        }\n    }\n\n    loadPlugins(pkg) {\n        try {\n            Object.keys(pkg.dependencies).forEach(name => {\n                this.loadPlugin(name)\n            })\n            this.done()\n        } catch (e) {\n            console.log(e)\n            this.done(e)\n        }\n    }\n\n    load() {\n        const pkg = this.getPackage()\n        const checkRes: checkRes = check.sync({\n            packageDir: pluginsPath\n        })\n        if (checkRes.depsWereOk) {\n            this.loadPlugins(pkg)\n        }\n        process.chdir(pluginsPath)\n        npm.load({\n            save: true\n        }, (err, res) => {\n            if (err) return this.done(err)\n            npm.commands.update([], (err, res) => {\n                if (err) return this.done(err)\n                if (!checkRes.depsWereOk) this.loadPlugins(pkg)\n            })\n        })\n    }\n}\n\nexport class PluginManager {\n    readonly jumpFm: JumpFm\n\n    constructor(jumpFm) {\n        this.jumpFm = jumpFm\n    }\n\n    loadAndUpdatePlugins = (done: (err?) => void) => {\n        this.jumpFm.statusBar.msg('plugins')\n            .setType('info')\n            .setText('Downloading plugins (can take a while)...')\n            .setTooltip('This might take some time')\n\n        const pluginLoader = new PluginsLoader(this.jumpFm, done)\n\n        pluginLoader.load()\n        watch(pluginsPackageJson, () => {\n            pluginLoader.load()\n        })\n    }\n}"
  },
  {
    "path": "ts/Settings.ts",
    "content": "import { Settings as SettingsApi } from 'jumpfm-api'\n\nimport { settings, saveSettings } from './files'\n\ntype Type = 'string' | 'number'\n\nexport class Settings implements SettingsApi {\n    private get = <T>(type: Type) => (key: string, defaultValue: T) => {\n        const val = settings[key]\n        if (val && (typeof val === type)) return val\n        settings[key] = defaultValue\n        saveSettings(settings)\n        return defaultValue\n    }\n\n    getNum = this.get<number>('number')\n    getStr = this.get<string>('string')\n}"
  },
  {
    "path": "ts/StatusBar.ts",
    "content": "import { StatusBar as StatusBarApi, Msg as MsgAPi } from 'jumpfm-api'\n\nclass Msg implements MsgAPi {\n    readonly divMsg: HTMLDivElement = document.createElement('div')\n\n    constructor() {\n        this.divMsg.className = 'msg'\n    }\n\n    setType = (type: \"info\" | \"warn\" | \"err\") => {\n        this.divMsg.setAttribute('type', type)\n        return this\n    }\n\n    setText = (txt: string) => {\n        this.divMsg.textContent = txt\n        return this\n    }\n\n    setTooltip = (txt: string) => {\n        this.divMsg.setAttribute('data-title', txt)\n        return this\n    }\n\n    setClearTimeout = (timeout: number) => {\n        setTimeout(() => this.setText(''), timeout)\n        return this\n    }\n\n    setAttr = (name: string, b: boolean) => {\n        if (b)\n            this.divMsg.setAttribute(name, '')\n        else\n            this.divMsg.removeAttribute(name)\n\n        return this\n    }\n}\n\nclass Button {\n    readonly a: HTMLAnchorElement = document.createElement('a')\n    private readonly i = document.createElement('i')\n\n    constructor(faIcon: string, tooltip: string) {\n        this.a.className = 'btn'\n        this.a.setAttribute('data-title', tooltip)\n        this.a.target = 'about:blank'\n        this.i.className = `fa ${faIcon}`\n        this.a.appendChild(this.i)\n    }\n}\n\nexport class StatusBar implements StatusBarApi {\n    private readonly divMsgs: HTMLDivElement = document\n        .getElementById('statusbar-msgs') as HTMLDivElement\n\n    private readonly divButtons: HTMLDivElement = document\n        .getElementById('statusbar-buttons') as HTMLDivElement\n\n    private readonly msgs: { [name: string]: Msg } = {}\n\n    msg = (name: string): MsgAPi => {\n        if (this.msgs[name]) return this.msgs[name]\n        const msg = new Msg()\n        this.msgs[name] = msg\n        this.divMsgs.appendChild(msg.divMsg)\n        return msg\n    }\n\n    clear = (name: string) => {\n        const msg = this.msgs[name]\n        if (!msg) return\n        msg.setText('')\n    }\n    msgClear = this.clear\n\n    buttonAdd(faIcon: string, tooltip: string, action: () => void) {\n        const a = new Button(faIcon, tooltip).a\n        this.divButtons.appendChild(a)\n        a.addEventListener('click', action, false)\n    }\n}"
  },
  {
    "path": "ts/files.ts",
    "content": "import * as fs from 'fs-extra';\nimport * as homedir from 'homedir';\nimport * as path from 'path';\n\nexport interface Plugins {\n    name: string\n    version: string\n    dependencies: { [name: string]: string }\n}\n\nconst load = (path: string) => {\n    try {\n        return require(path)\n    } catch (e) {\n        console.log(e)\n        return {}\n    }\n}\n\nconst save = <T>(path: string) => (obj: T) => {\n    fs.writeFileSync(path, JSON.stringify(obj, null, 4))\n    return obj;\n}\n\nexport const packageJson = require('../package.json')\n\nexport const root = path.join(homedir(), \".jumpfm\")\nexport const pluginsPath = path.join(root, 'plugins')\nif (!fs.existsSync(pluginsPath))\n    fs.mkdirpSync(pluginsPath)\n\nexport const pluginsPackageJson = path.join(pluginsPath, 'package.json')\nexport const settingsPath = path.join(root, 'settings.json')\nexport const keyboardPath = path.join(root, 'keyboard.json')\n\nexport const settings = load(settingsPath)\nexport const keyboard = load(keyboardPath)\n\nexport const saveSettings = save(settingsPath)\nexport const saveKeyboard = () => save(keyboardPath)(keyboard)\nexport const savePlugins = save<Plugins>(pluginsPackageJson)\n\n\nexport const getKeys = (actionName: string, defaultKeys: string[]): string[] => {\n    const keys = keyboard[actionName]\n    if (keys && Array.isArray(keys)) return keys\n    keyboard[actionName] = defaultKeys\n    return defaultKeys\n}\n"
  },
  {
    "path": "ts/icons.ts",
    "content": "\nconst icons = require('../icons.json')\nconst extensions = {}\nfor (var icon in icons) {\n    icons[icon].forEach(ext => {\n        extensions[ext] = icon\n    });\n}\n\nexport const getExtIcon = (ext: string) => {\n    const icon = extensions[ext]\n    if (icon) return 'file-icons/file_type_' + icon + '.svg'\n}"
  },
  {
    "path": "ts/main.ts",
    "content": "document.addEventListener('DOMContentLoaded', () => {\n    console.time('main')\n    new (require('./js/JumpFm.js').JumpFm)(require('electron').remote.getGlobal('argv'))\n    console.timeEnd('main')\n}, false)"
  },
  {
    "path": "ts/shortway.ts",
    "content": "const keyCodes: { [name: string]: number } = {\n    backspace: 8,\n    tab: 9,\n    enter: 13,\n    pause: 19,\n    esc: 27,\n    space: 32,\n    pageup: 33,\n    pagedown: 34,\n    end: 35,\n    home: 36,\n    left: 37,\n    up: 38,\n    right: 39,\n    down: 40,\n    insert: 45,\n    del: 46,\n    slash: 191,\n    0: 48,\n    1: 49,\n    2: 50,\n    3: 51,\n    4: 52,\n    5: 53,\n    6: 54,\n    7: 55,\n    8: 56,\n    9: 57,\n    a: 65,\n    b: 66,\n    c: 67,\n    d: 68,\n    e: 69,\n    f: 70,\n    g: 71,\n    h: 72,\n    i: 73,\n    j: 74,\n    k: 75,\n    l: 76,\n    m: 77,\n    n: 78,\n    o: 79,\n    p: 80,\n    q: 81,\n    r: 82,\n    s: 83,\n    t: 84,\n    u: 85,\n    v: 86,\n    w: 87,\n    x: 88,\n    y: 89,\n    z: 90,\n    f1: 112,\n    f2: 113,\n    f3: 114,\n    f4: 115,\n    f5: 116,\n    f6: 117,\n    f7: 118,\n    f8: 119,\n    f9: 120,\n    f10: 121,\n    f11: 122,\n    f12: 123,\n    numLock: 144,\n    scrollLock: 145,\n    ';': 186,\n    '=': 187,\n    ',': 188,\n    '-': 189,\n    '.': 190,\n    '/': 191,\n    '`': 192,\n    '[': 219,\n    '\\\\': 220,\n    ']': 221,\n    \"'\": 221\n}\n\nconst includes = (array, item) => array.indexOf(item) > -1\n\nexport const shortway = (command, callback) => {\n    const keys = command.split('+')\n    const key = keys.filter(key => keyCodes[key])[0]\n    const keyCode = keyCodes[key]\n    const ctrl = keys.some(key => key === 'ctrl')\n    const shift = keys.some(key => key === 'shift')\n    const alt = keys.some(key => key === 'alt')\n\n    if (!keyCode) throw new Error(`can't find keycode for command ${command}`)\n    return function (e) {\n        if (e.ctrlKey === ctrl &&\n            e.shiftKey === shift &&\n            e.altKey === alt &&\n            e.keyCode === keyCode) {\n            callback(e)\n            return false\n        }\n    }\n}"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"outDir\": \"js\",\n        \"target\": \"es2017\",\n        \"module\": \"commonjs\",\n        \"sourceMap\": true,\n        \"lib\": [\n            \"dom\",\n            \"es2017\",\n            \"es2015.promise\"\n        ]\n    }\n}"
  },
  {
    "path": "updates.json",
    "content": "{\n    \"linux-x64-prod\": {\n        \"readme\": \"JumpFm\",\n        \"update\": \"https://github.com/JumpFm/jumpfm/releases/download/v1.0.5/JumpFm-1.0.5-x86_64.AppImage\",\n        \"install\": \"https://github.com/JumpFm/jumpfm/releases/download/v1.0.5/JumpFm-1.0.5-x86_64.AppImage\",\n        \"version\": \"1.0.5\"\n    },\n    \"win32-x64-prod\": {\n        \"readme\": \"JumpFm\",\n        \"update\": \"https://github.com/JumpFm/jumpfm/releases/download/v1.0.5/jumpfm-setup-1.0.5.exe\",\n        \"install\": \"https://github.com/JumpFm/jumpfm/releases/download/v1.0.5/jumpfm-setup-1.0.5.exe\",\n        \"version\": \"1.0.5\"\n    },\n    \"darwin-x64-prod\": {\n        \"readme\": \"JumpFm\",\n        \"update\": \"https://github.com/heywoodlh/jumpfm/releases/download/v1.0.2/jumpfm-1.0.2.dmg\",\n        \"install\": \"https://github.com/heywoodlh/jumpfm/releases/download/v1.0.2/jumpfm-1.0.2.dmg\",\n        \"version\": \"1.0.2\"\n    }\n}"
  }
]